qspinlock with paravirt support
Subsystems (9)
cover qspinlock with paravirt support
Since Waiman seems incapable of doing simple things; here's my take on the paravirt crap.
The first few patches are taken from Waiman's latest series, but the virt support is completely new. Its primary aim is to not mess up the native code.
I've not stress tested it, but the virt and paravirt (kvm) cases boot on simple smp guests. I've not done Xen, but the patch should be simple and similar.
I ripped out all the unfair nonsense as its not at all required for paravirt and optimizations that make paravirt better at the cost of code clarity and/or native performance are just not worth it.
Also; if we were to ever add some of that unfair nonsense you do so _after_ you got the simple things working.
The thing I'm least sure about is the head tracking, I chose to do something different from what Waiman did, because his is O(nr_cpus) and had the assumption that guests have small nr_cpus. AFAIK this is not at all true. The biggest problem I have with what I did is that it contains wait loops itself.
PATCH 1/11 qspinlock: A simple generic 4-byte queue spinlock
From: Waiman Long <[email protected]>
This patch introduces a new generic queue spinlock implementation that can serve as an alternative to the default ticket spinlock. Compared with the ticket spinlock, this queue spinlock should be almost as fair as the ticket spinlock. It has about the same speed in single-thread and it can be much faster in high contention situations especially when the spinlock is embedded within the data structure to be protected.
Only in light to moderate contention where the average queue depth is around 1-3 will this queue spinlock be potentially a bit slower due to the higher slowpath overhead.
This queue spinlock is especially suit to NUMA machines with a large number of cores as the chance of spinlock contention is much higher in those machines. The cost of contention is also higher because of slower inter-node memory traffic.
Due to the fact that spinlocks are acquired with preemption disabled, the process will not be migrated to another CPU while it is trying to get a spinlock. Ignoring interrupt handling, a CPU can only be contending in one spinlock at any one time. Counting soft IRQ, hard IRQ and NMI, a CPU can only have a maximum of 4 concurrent lock waiting activities. By allocating a set of per-cpu queue nodes and used them to form a waiting queue, we can encode the queue node address into a much smaller 24-bit size (including CPU number and queue node index) leaving one byte for the lock.
Please note that the queue node is only needed when waiting for the lock. Once the lock is acquired, the queue node can be released to be used later.
--- include/asm-generic/qspinlock.h | 118 ++++++++++++++++++++ include/asm-generic/qspinlock_types.h | 61 ++++++++++ kernel/Kconfig.locks | 7 + kernel/locking/Makefile | 1 kernel/locking/mcs_spinlock.h | 1 kernel/locking/qspinlock.c | 197 ++++++++++++++++++++++++++++++++++ 6 files changed, 385 insertions(+) create mode 100644 include/asm-generic/qspinlock.h create mode 100644 include/asm-generic/qspinlock_types.h create mode 100644 kernel/locking/qspinlock.c
include/asm-generic/qspinlock.h new file +118 -0
@@ -0,0 +1,118 @@ +/* +* Queue spinlock +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* (C) Copyright 2013-2014 Hewlett-Packard Development Company, L.P. +* +* Authors: Waiman Long <[email protected]> +*/ +#ifndef __ASM_GENERIC_QSPINLOCK_H +#define __ASM_GENERIC_QSPINLOCK_H + +#include <asm-generic/qspinlock_types.h> + +/** +* queue_spin_is_locked - is the spinlock locked? +* @lock: Pointer to queue spinlock structure +* Return: 1 if it is locked, 0 otherwise +*/ +static __always_inline int queue_spin_is_locked(struct qspinlock *lock) +{ +return atomic_read (&lock->val); +} + +/** +* queue_spin_value_unlocked - is the spinlock structure unlocked? +* @lock: queue spinlock structure +* Return: 1 if it is unlocked, 0 otherwise +* +* N.B. Whenever there are tasks waiting for the lock, it is considered +* locked wrt the lockref code to avoid lock stealing by the lockref +* code and change things underneath the lock. This also allows some +* optimizations to be applied without conflict with lockref. +*/ +static __always_inline int queue_spin_value_unlocked(struct qspinlock lock) +{ +return !atomic_read (&lock.val); +} + +/** +* queue_spin_is_contended - check if the lock is contended +* @lock : Pointer to queue spinlock structure +* Return: 1 if lock contended, 0 otherwise +*/ +static __always_inline int queue_spin_is_contended(struct qspinlock *lock) +{ +return atomic_read (&lock->val) & ~_Q_LOCKED_MASK; +} +/** +* queue_spin_trylock - try to acquire the queue spinlock +* @lock : Pointer to queue spinlock structure +* Return: 1 if lock acquired, 0 if failed +*/ +static __always_inline int queue_spin_trylock(struct qspinlock *lock) +{ +if (!atomic_read (&lock->val) && + (atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL) ==0 )) +return 1 ; +return 0 ; +} + +extern void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val); + +/** +* queue_spin_lock - acquire a queue spinlock +* @lock: Pointer to queue spinlock structure +*/ +static __always_inline void queue_spin_lock(struct qspinlock *lock) +{ +u32 val; + + val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL); +if (likely (val ==0 )) +return ; +queue_spin_lock_slowpath (lock, val); +} + +#ifndef queue_spin_unlock +/** +* queue_spin_unlock - release a queue spinlock +* @lock : Pointer to queue spinlock structure +*/ +static __always_inline void queue_spin_unlock(struct qspinlock *lock) +{ +/* +* smp_mb__before_atomic() in order to guarantee release semantics +*/ +smp_mb__before_atomic_dec (); +atomic_sub (_Q_LOCKED_VAL, &lock->val); +} +#endif + +/* +* Initializier +*/ +#define __ARCH_SPIN_LOCK_UNLOCKED { ATOMIC_INIT(0) } + +/* +* Remapping spinlock architecture specific functions to the corresponding +* queue spinlock functions. +*/ +#define arch_spin_is_locked (l) queue_spin_is_locked(l) +#define arch_spin_is_contended (l) queue_spin_is_contended(l) +#define arch_spin_value_unlocked (l) queue_spin_value_unlocked(l) +#define arch_spin_lock (l) queue_spin_lock(l) +#define arch_spin_trylock (l) queue_spin_trylock(l) +#define arch_spin_unlock (l) queue_spin_unlock(l) +#define arch_spin_lock_flags (l, f) queue_spin_lock(l) + +#endif /* __ASM_GENERIC_QSPINLOCK_H */
include/asm-generic/qspinlock_types.h new file +61 -0
61 lines not on this page · raw message
kernel/Kconfig.locks +7 -0
@@ -224,6 +224,13 @@ config MUTEX_SPIN_ON_OWNER def_bool y depends on SMP && !DEBUG_MUTEXES && !PARISC +config ARCH_USE_QUEUE_SPINLOCK + bool + +config QUEUE_SPINLOCK + def_bool y if ARCH_USE_QUEUE_SPINLOCK + depends on SMP && !PARAVIRT_SPINLOCKS + config ARCH_USE_QUEUE_RWLOCK bool
kernel/locking/Makefile +1 -0
@@ -16,6 +16,7 @@ endif obj-$(CONFIG_SMP) += spinlock.o obj-$(CONFIG_SMP) += lglock.o obj-$(CONFIG_PROVE_LOCKING) += spinlock.o +obj-$(CONFIG_QUEUE_SPINLOCK) += qspinlock.o obj-$(CONFIG_RT_MUTEXES) += rtmutex.o obj-$(CONFIG_DEBUG_RT_MUTEXES) += rtmutex-debug.o obj-$(CONFIG_RT_MUTEX_TESTER) += rtmutex-tester.o
kernel/locking/mcs_spinlock.h +1 -0
@@ -17,6 +17,7 @@struct mcs_spinlock {struct mcs_spinlock *next;int locked;/* 1 if lock acquired */ +int count; };#ifndef arch_mcs_spin_lock_contended
kernel/locking/qspinlock.c new file +197 -0
197 lines not on this page · raw message
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
Quoted 28 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
This patch introduces a new generic queue spinlock implementation that can serve as an alternative to the default ticket spinlock. Compared with the ticket spinlock, this queue spinlock should be almost as fair as the ticket spinlock. It has about the same speed in single-thread and it can be much faster in high contention situations especially when the spinlock is embedded within the data structure to be protected.
Only in light to moderate contention where the average queue depth is around 1-3 will this queue spinlock be potentially a bit slower due to the higher slowpath overhead.
This queue spinlock is especially suit to NUMA machines with a large number of cores as the chance of spinlock contention is much higher in those machines. The cost of contention is also higher because of slower inter-node memory traffic.
Due to the fact that spinlocks are acquired with preemption disabled, the process will not be migrated to another CPU while it is trying to get a spinlock. Ignoring interrupt handling, a CPU can only be contending in one spinlock at any one time. Counting soft IRQ, hard IRQ and NMI, a CPU can only have a maximum of 4 concurrent lock waiting activities. By allocating a set of per-cpu queue nodes and used them to form a waiting queue, we can encode the queue node address into a much smaller 24-bit size (including CPU number and queue node index) leaving one byte for the lock.
Quoted 6 lines
Please note that the queue node is only needed when waiting for the lock. Once the lock is acquired, the queue node can be released to be used later.
Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>
Thank you for the repost. I have some questions about the implementation that hopefully will be easy to answer and said answers I hope can be added in the code to enlighten other folks.
See below.
.. snip..
kernel/locking/mcs_spinlock.h line 20
Quoted 9 lines
Index:
linux-2.6/kernel/locking/mcs_spinlock.h
===================================================================kernel/locking/mcs_spinlock.h +1
-0@@ -17,6 +17,7 @@struct mcs_spinlock {struct mcs_spinlock *next;int locked;/* 1 if lock acquired */ +int count;
This could use a comment.
Quoted 71 lines
kernel/locking/mcs_spinlock.h +0
-0};#ifndef arch_mcs_spin_lock_contendedIndex:
linux-2.6/kernel/locking/qspinlock.c
===================================================================kernel/locking/qspinlock.c new file +63
-0@@ -0,0 +1,197 @@ +/* +* Queue spinlock +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* (C) Copyright 2013-2014 Hewlett-Packard Development Company, L.P. +* +* Authors: Waiman Long <[email protected]> +* Peter Zijlstra <[email protected]> +*/ +#include <linux/smp.h> +#include <linux/bug.h> +#include <linux/cpumask.h> +#include <linux/percpu.h> +#include <linux/hardirq.h> +#include <linux/mutex.h> +#include <asm/qspinlock.h> + +/* +* The basic principle of a queue-based spinlock can best be understood +* by studying a classic queue-based spinlock implementation called the +* MCS lock. The paper below provides a good description for this kind +* of lock. +* +* http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf +* +* This queue spinlock implementation is based on the MCS lock, however to make +* it fit the 4 bytes we assume spinlock_t to be, and preserve its existing +* API, we must modify it some. +* +* In particular; where the traditional MCS lock consists of a tail pointer +* (8 bytes) and needs the next pointer (another 8 bytes) of its own node to +* unlock the next pending (next->locked), we compress both these: {tail, +* next->locked} into a single u32 value. +* +* Since a spinlock disables recursion of its own context and there is a limit +* to the contexts that can nest; namely: task, softirq, hardirq, nmi, we can +* encode the tail as and index indicating this context and a cpu number. +* +* We can further change the first spinner to spin on a bit in the lock word +* instead of its node; whereby avoiding the need to carry a node from lock to +* unlock, and preserving API. +*/ + +#include "mcs_spinlock.h" + +/* +* Per-CPU queue node structures; we can never have more than 4 nested +* contexts: task, softirq, hardirq, nmi. +* +* Exactly fits one cacheline. +*/ +static DEFINE_PER_CPU_ALIGNED(struct mcs_spinlock, mcs_nodes[4]); + +/*
kernel/locking/qspinlock.c line 73
Quoted 10 lines
kernel/locking/qspinlock.c new file +10
-0+* We must be able to distinguish between no-tail and the tail at 0:0, +* therefore increment the cpu number by one. +*/ + +static inline u32 encode_tail (int cpu,int idx) +{ +u32 tail; + + tail = (cpu +1 ) << _Q_TAIL_CPU_OFFSET; + tail |= idx << _Q_TAIL_IDX_OFFSET;/* assume < 4 */
Should there an
ASSSERT (idx < 4)
just in case we screw up somehow (I can't figure out how, but that is partially why ASSERTS are added).
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+ +return tail; +} + +static inline struct mcs_spinlock *decode_tail (u32 tail) +{ +int cpu = (tail >> _Q_TAIL_CPU_OFFSET) -1 ; +int idx = (tail & _Q_TAIL_IDX_MASK) >> _Q_TAIL_IDX_OFFSET;
kernel/locking/qspinlock.c line 91
Quoted 10 lines
kernel/locking/qspinlock.c new file +10
-0+ +return per_cpu_ptr (&mcs_nodes[idx], cpu); +} + +/** +* queue_spin_lock_slowpath - acquire the queue spinlock +* @lock: Pointer to queue spinlock structure +* @val: Current value of the queue spinlock 32-bit word +* +* (queue tail, lock bit)
Except it is not a lock bit. It is a lock uint8_t.
Is the queue tail at this point the composite of 'cpu|idx'?
kernel/locking/qspinlock.c line 98
Quoted 7 lines
kernel/locking/qspinlock.c new file +7
-0+* +* fast : slow : unlock +* : : +* uncontended (0,0) --:--> (0,1) --------------------------------:--> (*,0) +* : | ^--------. / : +* : v \ | : +* uncontended : (n,x) --+--> (n,0) | :
So many CPUn come in right? Is 'n' for the number of CPUs?
kernel/locking/qspinlock.c line 102
Quoted 4 lines
kernel/locking/qspinlock.c new file +4
-0+* queue : | ^--' | : +* : v | : +* contended : (*,x) --+--> (*,0) -----> (*,1) ---' : +* queue : ^--' :
And here um, what are the '*' for? Are they the four different types of handlers that can be nested? So task, sofitrq, hardisk, and nmi?
kernel/locking/qspinlock.c line 114
Quoted 12 lines
kernel/locking/qspinlock.c new file +12
-0+* +*/ +void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val) +{ +struct mcs_spinlock *prev, *next, *node; +u32 new, old, tail; +int idx; + +BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); + + node =this_cpu_ptr (&mcs_nodes[0 ]); + idx = node->count++;
If this is the first time we enter this, wouldn't idx end up being 1?
kernel/locking/qspinlock.c line 117
Quoted 3 lines
kernel/locking/qspinlock.c new file +3
-0+ tail =encode_tail (smp_processor_id (), idx); + + node += idx;
Meaning we end up skipping the 'mcs_nodes[0]' one altogether - even on the first 'level' (task, softirq, hardirq, nmi)? Won't that cause us to blow past the array when we are nested at the nmi handler?
kernel/locking/qspinlock.c line 125
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+ node->locked =0 ; + node->next =NULL ; + +/* +* trylock || xchg(lock, node) +* +* 0,0 -> 0,1 ; trylock +* p,x -> n,x ; prev = xchg(lock, node)
I looked at that for 10 seconds and I was not sure what you meant. Is this related to the MCS document you had pointed to? It would help if you mention that the comments follow the document. (But they don't seem to)
I presume what you mean is that if we are the next after the lock-holder we need only to update the 'next' (or the composite value of smp_processor_idx | idx) to point to us.
As in, swap the 'L' with 'I' (looking at the doc)
kernel/locking/qspinlock.c line 129
Quoted 4 lines
kernel/locking/qspinlock.c new file +4
-0+*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val)
Could you add a comment here, like this:
/*
* N.B. Initially 'val' will have some value (as we are called
* after the _Q_LOCKED_VAL could not be set by queue_spin_lock).
* But on subsequent iterations, either the lock holder will
* decrement the val (queue_spin_unlock - to zero) and we
* needn't to record our status in the queue as we have set the
* Q_LOCKED_VAL (new) and are the lock holder. Or we are next
* in line and need to record our 'next' (aka, smp_processor_id() | idx)
* position. */
*/
Quoted 33 lines
kernel/locking/qspinlock.c new file +33
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; + +/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked); + } + +/* +* we're at the head of the waitqueue, wait for the owner to go away. +* +* *,x -> *,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); +
kernel/locking/qspinlock.c line 172
Quoted 10 lines
kernel/locking/qspinlock.c new file +10
-0+/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;
You lost me here. If we are at the head of the queue, and the owner has called queue_spin_unlock (hence made us get out of the 'val = atomic_read' loop, how can val != tail?
I suspect it has something to do with the comment, but I am still unsure what it means.
Could you help a bit in explaining it in English please?
kernel/locking/qspinlock.c line 184
Quoted 12 lines
kernel/locking/qspinlock.c new file +12
-0+ + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* contended path; wait for next, release. +*/ +if (new != _Q_LOCKED_VAL) {
Hm, wouldn't it be just easier to do a 'goto restart' where restart label points at the first loop statement? Ah never mind - we have already inserted ourselves in the previous's node.
But that is confusing - we have done: "prev->next = node;"
And then exited out of 'val = atomic_read(&lock->val))' which suggests that queue_spin_unlock has called us. How can we be contended again?
Thanks!
Quoted 15 lines
kernel/locking/qspinlock.c new file +13
-0+while (!(next =ACCESS_ONCE (node->next))) +cpu_relax (); + +arch_mcs_spin_unlock_contended (&next->locked); + } + +release: +/* +* release the node +*/ +this_cpu_dec (mcs_nodes[0 ].count); +} +EXPORT_SYMBOL (queue_spin_lock_slowpath);
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
Quoted 15 lines
Quoted 15 lines
kernel/locking/qspinlock.c new file +15
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; +
Quoted 8 lines
kernel/locking/qspinlock.c line 152
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked);
Could you add a comment here:
/* We are spinning forever until the previous node updates locked - which it does once the it has updated lock->val with our tail number. */
Quoted 21 lines
Quoted 20 lines
kernel/locking/qspinlock.c new file +20
-0+ } + +/* +* we're at the head of the waitqueue, wait for the owner to go away. +* +* *,x -> *,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); + +/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;
..snip..
Quoted 2 lines
Could you help a bit in explaining it in English please?
After looking at the assembler code I finally figured out how we can get here. And the 'contended' part threw me off. Somehow I imagined there are two more more CPUs stampeding here and trying to update the lock->val. But in reality the other CPUs are stuck in the arch_mcs_spin_lock_contended spinning on their local value.
Perhaps you could add this comment.
/* Once queue_spin_unlock is called (which _subtracts_ _Q_LOCKED_VAL from the lock->val and still preserving the tail data), the winner gets to claim the ticket. Since we still need the other CPUs to continue and preserve the strict ordering in which they setup node->next, we:
1) update lock->val to the tail value (so tail CPU and its index) with
_Q_LOCKED_VAL.
2). Once we are done, we poke the other CPU (the one that linked to
us) by writting to node->locked (below) so they can make progress and
loop on lock->val changing from _Q_LOCKED_MASK to zero).*/
Peter Zijlstra
maintainerRe: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
Quoted 26 lines · Konrad Rzeszutek Wilk
Quoted 15 lines
Quoted 15 lines
kernel/locking/qspinlock.c new file +15
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; +Quoted 8 lines
kernel/locking/qspinlock.c line 152
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked);Could you add a comment here:
Quoted 2 lines
/* We are spinning forever until the previous node updates locked - which it does once the it has updated
lock->valwith our tail number. */
That's incorrect -- or at least, I understand that to be incorrect. The previous node will not have changed the tail to point to us. You always change to tail to point to yourself, seeing how you add yourself to the tail.
Is the existing comment any better if I s/wait./wait for it to release us./ ?
Quoted 13 lines
Quoted 11 lines
Quoted 10 lines
kernel/locking/qspinlock.c new file +10
-0+/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;..snip..
Quoted 1 line
Quoted 8 lines
Quoted 1 line
Could you help a bit in explaining it in English please?
After looking at the assembler code I finally figured out how we can get here. And the 'contended' part threw me off. Somehow I imagined there are two more more CPUs stampeding here and trying to update the
lock->val. But in reality the other CPUs are stuck in thearch_mcs_spin_lock_contendedspinning on their local value.
Well, the lock as a whole is contended (there's >1 waiters), and the point of MCS style locks it to make sure they're not actually pounding on the same cacheline. So the whole thing is consistent.
Quoted 5 lines
Perhaps you could add this comment.
/* Once
queue_spin_unlockis called (which _subtracts__Q_LOCKED_VALfrom thelock->valand still preserving the tail data), the winner gets to claim the ticket.
There's no tickets :/
Quoted 4 lines
Since we still need the other CPUs to continue and
preserve the strict ordering in which they setupnode->next, we:
1) updatelock->valto the tail value (so tail CPU and its index) with_Q_LOCKED_VAL.
We don't, we preserve the tail value, unless we're the tail, in which case we clear the tail.
Quoted 3 lines
2). Once we are done, we poke the other CPU (the one that linked to
us) by writting to node->locked (below) so they can make progress and loop on lock->val changing from _Q_LOCKED_MASK to zero).
_If_ there was another cpu, ie. the tail didn't point to us.
---
I don't do well with natural language comments like that; they tend to confuse me more than anything.
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
Quoted 30 lines · Peter Zijlstra
Quoted 26 lines · Konrad Rzeszutek Wilk
Quoted 15 lines
Quoted 15 lines
kernel/locking/qspinlock.c new file +15
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; +Quoted 8 lines
kernel/locking/qspinlock.c line 152
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked);Could you add a comment here:
Quoted 2 lines
/* We are spinning forever until the previous node updates locked - which it does once the it has updated
lock->valwith our tail number. */
Quoted 7 lines
That's incorrect -- or at least, I understand that to be incorrect. The previous node will not have changed the tail to point to us. You always change to tail to point to yourself, seeing how you add yourself to the tail.
Is the existing comment any better if I s/wait./wait for it to release us./ ?
Yes!
Quoted 27 lines
Quoted 14 lines
Quoted 12 lines
Quoted 11 lines
kernel/locking/qspinlock.c new file +10
-0+/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;..snip..
Quoted 1 line
Quoted 8 lines
Quoted 1 line
Could you help a bit in explaining it in English please?
After looking at the assembler code I finally figured out how we can get here. And the 'contended' part threw me off. Somehow I imagined there are two more more CPUs stampeding here and trying to update the
lock->val. But in reality the other CPUs are stuck in thearch_mcs_spin_lock_contendedspinning on their local value.Well, the lock as a whole is contended (there's >1 waiters), and the point of MCS style locks it to make sure they're not actually pounding on the same cacheline. So the whole thing is consistent.
Quoted 7 lines
Quoted 5 lines
Perhaps you could add this comment.
/* Once
queue_spin_unlockis called (which _subtracts__Q_LOCKED_VALfrom thelock->valand still preserving the tail data), the winner gets to claim the ticket.There's no tickets :/
s/ticket/be first in line/ ?
Quoted 6 lines
Quoted 5 lines
Since we still need the other CPUs to continue and
preserve the strict ordering in which they setupnode->next, we:
1) updatelock->valto the tail value (so tail CPU and its index) with_Q_LOCKED_VAL.
Quoted 8 lines
We don't, we preserve the tail value, unless we're the tail, in which case we clear the tail.
Quoted 3 lines
2). Once we are done, we poke the other CPU (the one that linked to
us) by writting to node->locked (below) so they can make progress and loop on lock->val changing from _Q_LOCKED_MASK to zero)._If_ there was another cpu, ie. the tail didn't point to us.
<nods>
Quoted 6 lines
---
I don't do well with natural language comments like that; they tend to confuse me more than anything.
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Peter ZijlstraQuoted 30 lines · Peter Zijlstra
Quoted 26 lines · Konrad Rzeszutek Wilk
Quoted 15 lines
Quoted 15 lines
kernel/locking/qspinlock.c new file +15
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; +Quoted 8 lines
kernel/locking/qspinlock.c line 152
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked);Could you add a comment here:
Quoted 2 lines
/* We are spinning forever until the previous node updates locked - which it does once the it has updated
lock->valwith our tail number. */
Quoted 7 lines
That's incorrect -- or at least, I understand that to be incorrect. The previous node will not have changed the tail to point to us. You always change to tail to point to yourself, seeing how you add yourself to the tail.
Is the existing comment any better if I s/wait./wait for it to release us./ ?
Yes!
Quoted 27 lines
Quoted 14 lines
Quoted 12 lines
Quoted 11 lines
kernel/locking/qspinlock.c new file +10
-0+/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;..snip..
Quoted 1 line
Quoted 8 lines
Quoted 1 line
Could you help a bit in explaining it in English please?
After looking at the assembler code I finally figured out how we can get here. And the 'contended' part threw me off. Somehow I imagined there are two more more CPUs stampeding here and trying to update the
lock->val. But in reality the other CPUs are stuck in thearch_mcs_spin_lock_contendedspinning on their local value.Well, the lock as a whole is contended (there's >1 waiters), and the point of MCS style locks it to make sure they're not actually pounding on the same cacheline. So the whole thing is consistent.
Quoted 7 lines
Quoted 5 lines
Perhaps you could add this comment.
/* Once
queue_spin_unlockis called (which _subtracts__Q_LOCKED_VALfrom thelock->valand still preserving the tail data), the winner gets to claim the ticket.There's no tickets :/
s/ticket/be first in line/ ?
Quoted 6 lines
Quoted 5 lines
Since we still need the other CPUs to continue and
preserve the strict ordering in which they setupnode->next, we:
1) updatelock->valto the tail value (so tail CPU and its index) with_Q_LOCKED_VAL.
Quoted 8 lines
We don't, we preserve the tail value, unless we're the tail, in which case we clear the tail.
Quoted 3 lines
2). Once we are done, we poke the other CPU (the one that linked to
us) by writting to node->locked (below) so they can make progress and loop on lock->val changing from _Q_LOCKED_MASK to zero)._If_ there was another cpu, ie. the tail didn't point to us.
<nods>
Quoted 6 lines
---
I don't do well with natural language comments like that; they tend to confuse me more than anything.
Peter Zijlstra
maintainerRe: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Konrad Rzeszutek WilkQuoted 26 lines · Konrad Rzeszutek Wilk
Quoted 15 lines
Quoted 15 lines
kernel/locking/qspinlock.c new file +15
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; +Quoted 8 lines
kernel/locking/qspinlock.c line 152
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked);Could you add a comment here:
Quoted 2 lines
/* We are spinning forever until the previous node updates locked - which it does once the it has updated
lock->valwith our tail number. */
That's incorrect -- or at least, I understand that to be incorrect. The previous node will not have changed the tail to point to us. You always change to tail to point to yourself, seeing how you add yourself to the tail.
Is the existing comment any better if I s/wait./wait for it to release us./ ?
Quoted 13 lines
Quoted 11 lines
Quoted 10 lines
kernel/locking/qspinlock.c new file +10
-0+/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;..snip..
Quoted 1 line
Quoted 8 lines
Quoted 1 line
Could you help a bit in explaining it in English please?
After looking at the assembler code I finally figured out how we can get here. And the 'contended' part threw me off. Somehow I imagined there are two more more CPUs stampeding here and trying to update the
lock->val. But in reality the other CPUs are stuck in thearch_mcs_spin_lock_contendedspinning on their local value.
Well, the lock as a whole is contended (there's >1 waiters), and the point of MCS style locks it to make sure they're not actually pounding on the same cacheline. So the whole thing is consistent.
Quoted 5 lines
Perhaps you could add this comment.
/* Once
queue_spin_unlockis called (which _subtracts__Q_LOCKED_VALfrom thelock->valand still preserving the tail data), the winner gets to claim the ticket.
There's no tickets :/
Quoted 4 lines
Since we still need the other CPUs to continue and
preserve the strict ordering in which they setupnode->next, we:
1) updatelock->valto the tail value (so tail CPU and its index) with_Q_LOCKED_VAL.
We don't, we preserve the tail value, unless we're the tail, in which case we clear the tail.
Quoted 3 lines
2). Once we are done, we poke the other CPU (the one that linked to
us) by writting to node->locked (below) so they can make progress and loop on lock->val changing from _Q_LOCKED_MASK to zero).
_If_ there was another cpu, ie. the tail didn't point to us.
---
I don't do well with natural language comments like that; they tend to confuse me more than anything.
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Konrad Rzeszutek WilkQuoted 15 lines
Quoted 15 lines
kernel/locking/qspinlock.c new file +15
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; +
Quoted 8 lines
kernel/locking/qspinlock.c line 152
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked);
Could you add a comment here:
/* We are spinning forever until the previous node updates locked - which it does once the it has updated lock->val with our tail number. */
Quoted 21 lines
Quoted 20 lines
kernel/locking/qspinlock.c new file +20
-0+ } + +/* +* we're at the head of the waitqueue, wait for the owner to go away. +* +* *,x -> *,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); + +/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;
..snip..
Quoted 2 lines
Could you help a bit in explaining it in English please?
After looking at the assembler code I finally figured out how we can get here. And the 'contended' part threw me off. Somehow I imagined there are two more more CPUs stampeding here and trying to update the lock->val. But in reality the other CPUs are stuck in the arch_mcs_spin_lock_contended spinning on their local value.
Perhaps you could add this comment.
/* Once queue_spin_unlock is called (which _subtracts_ _Q_LOCKED_VAL from the lock->val and still preserving the tail data), the winner gets to claim the ticket. Since we still need the other CPUs to continue and preserve the strict ordering in which they setup node->next, we:
1) update lock->val to the tail value (so tail CPU and its index) with
_Q_LOCKED_VAL.
2). Once we are done, we poke the other CPU (the one that linked to
us) by writting to node->locked (below) so they can make progress and
loop on lock->val changing from _Q_LOCKED_MASK to zero).*/
Peter Zijlstra
maintainerRe: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Konrad Rzeszutek WilkQuoted 11 lines · Konrad Rzeszutek Wilk
kernel/locking/mcs_spinlock.h line 20
Quoted 9 lines
Index:
linux-2.6/kernel/locking/mcs_spinlock.h
===================================================================kernel/locking/mcs_spinlock.h +1
-0@@ -17,6 +17,7 @@struct mcs_spinlock {struct mcs_spinlock *next;int locked;/* 1 if lock acquired */ +int count;This could use a comment.
like so?
int count; /* nesting count, see qspinlock.c */
Quoted 9 lines
kernel/locking/mcs_spinlock.h
Quoted 6 lines
kernel/locking/mcs_spinlock.h +6
-0+static inline u32 encode_tail (int cpu,int idx) +{ +u32 tail; + + tail = (cpu +1 ) << _Q_TAIL_CPU_OFFSET; + tail |= idx << _Q_TAIL_IDX_OFFSET;/* assume < 4 */ Should there an
Quoted 4 lines
ASSSERT (idx < 4)
just in case we screw up somehow (I can't figure out how, but that is partially why ASSERTS are added).
#ifdef CONFIG_DEBUG_SPINLOCK BUG_ON(idx > 3);
#endif
might do, I suppose.
Quoted 8 lines
Quoted 6 lines
+/**
+ *queue_spin_lock_slowpath- acquire the queue spinlock
+ * @lock: Pointer to queue spinlock structure
+ * @val: Current value of the queue spinlock 32-bit word
+ *
+ * (queue tail, lock bit)Except it is not a lock bit. It is a lock
uint8_t.
It is indeed, although that's an accident of implementation. I could do s/bit// and not mention the entire storage angle at all?
Quoted 1 line
Is the queue tail at this point the composite of 'cpu|idx'?
Yes, as per {en,de}code_tail() above.
Quoted 9 lines
Quoted 7 lines
+ *
+ * fast : slow : unlock + * : : + * uncontended (0,0) --:--> (0,1) --------------------------------:--> (*,0) + * : | ^--------. / : + * : v \ | : + * uncontended : (n,x) --+--> (n,0) | :So many CPUn come in right? Is 'n' for the number of CPUs?
Nope, 'n' for any one specific tail, in particular the first one to arrive. This is the 'uncontended queue' case as per the label, so we need a named value for the first, in order to distinguish between the state to the right (same tail, but unlocked) and the state below (different tail).
Quoted 8 lines
Quoted 4 lines
+ * queue : | ^--' | : + * : v | : + * contended : (*,x) --+--> (*,0) -----> (*,1) ---' : + * queue : ^--' :And here um, what are the '*' for? Are they the four different types of handlers that can be nested? So task, sofitrq, hardisk, and nmi?
'*' as in wildcard, any tail, specifically not 'n'.
Quoted 5 lines
Quoted 5 lines
kernel/locking/mcs_spinlock.h +5
-0+void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val) +{ +struct mcs_spinlock *prev, *next, *node; +u32 new, old, tail; +int idx;
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 5 lines
kernel/locking/mcs_spinlock.h +5
-0+ +BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); + + node =this_cpu_ptr (&mcs_nodes[0 ]); + idx = node->count++;If this is the first time we enter this, wouldn't idx end up being 1?
Nope, postfix ++ returns first and increments later.
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 3 lines
kernel/locking/mcs_spinlock.h +3
-0+ tail =encode_tail (smp_processor_id (), idx); + + node += idx;Meaning we end up skipping the 'mcs_nodes[0]' one altogether - even on the first 'level' (task, softirq, hardirq, nmi)? Won't that cause us to blow past the array when we are nested at the nmi handler?
Seeing how its all static storage, which is automagically initialized to 0, combined with the postfix ++ (as opposed to the prefix ++) we should be getting 0 here.
Quoted 14 lines
kernel/locking/mcs_spinlock.h
Quoted 8 lines
kernel/locking/mcs_spinlock.h +8
-0+ node->locked =0 ; + node->next =NULL ; + +/* +* trylock || xchg(lock, node) +* +* 0,0 -> 0,1 ; trylock +* p,x -> n,x ; prev = xchg(lock, node) I looked at that for 10 seconds and I was not sure what you meant. Is this related to the MCS document you had pointed to? It would help if you mention that the comments follow the document. (But they don't seem to)
Quoted 5 lines
I presume what you mean is that if we are the next after the lock-holder we need only to update the 'next' (or the composite value of
smp_processor_idx| idx) to point to us.As in, swap the 'L' with 'I' (looking at the doc)
They are the 'tail','lock' tuples, so this composite atomic operation completes either:
0,0 -> 0,1 -- we had no tail, not locked; into: no tail, locked.
OR
p,x -> n,x -- tail was p; into: tail is n; preserving locked.
Quoted 7 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +4
-0+*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val)Could you add a comment here, like this:
Quoted 10 lines
/*
* N.B. Initially 'val' will have some value (as we are called
* after the_Q_LOCKED_VALcould not be set byqueue_spin_lock).
* But on subsequent iterations, either the lock holder will
* decrement the val (queue_spin_unlock- to zero) and we
* needn't to record our status in the queue as we have set the
*Q_LOCKED_VAL(new) and are the lock holder. Or we are next
* in line and need to record our 'next' (aka,smp_processor_id()| idx)
* position. */
*/
The idea was that:
0,0 -> 0,1
p,x -> n,x
Completely covers what this composite atomic does.
Quoted 39 lines
Quoted 39 lines
kernel/locking/mcs_spinlock.h +39
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; + +/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked); + } + +/* +* we're at the head of the waitqueue, wait for the owner to go away. +* +* *,x -> *,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); + +/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +4
-0+for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;You lost me here. If we are at the head of the queue, and the owner has called
queue_spin_unlock(hence made us get out of the 'val =atomic_read' loop, how can val != tail?
Remember:
Quoted 1 line
kernel/locking/mcs_spinlock.h
Quoted 1 line
kernel/locking/mcs_spinlock.h +1
-0+ tail =encode_tail (smp_processor_id (), idx);
So if value != tail, that means the tail pointer doesn't point to us anymore, another cpu/idx queued itself and is now last.
Quoted 4 lines
I suspect it has something to do with the comment, but I am still unsure what it means.
Could you help a bit in explaining it in English please?
(refer to the state diagram, if we count states left->right, top->bottom, then this is: 5->2 or 7->8
n,0 -> 0,1:
the lock is free and the tail points to the first queued; this means that unqueueing implies wiping the tail, at the same time, acquire the lock.
*,0 -> *,1:
the lock is free and the tail doesn't point to the first queued; this means that unqueueing doesn't touch the tail pointer but only sets the lock.
Quoted 18 lines
kernel/locking/mcs_spinlock.h
Quoted 12 lines
kernel/locking/mcs_spinlock.h +12
-0+ + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* contended path; wait for next, release. +*/ +if (new != _Q_LOCKED_VAL) {Hm, wouldn't it be just easier to do a 'goto restart' where restart label points at the first loop statement? Ah never mind - we have already inserted ourselves in the previous's node.
Quoted 5 lines
But that is confusing - we have done: "
prev->next= node;"And then exited out of 'val =
atomic_read(&lock->val))' which suggests thatqueue_spin_unlockhas called us. How can we be contended again?
We're not contended again; we're in the 'contended queued' case, which means that 'tail' didn't point to us anymore, in that case, we must kick our next node such that it will now drop out of arch_mcs_spin_lock_contended() and goes wait on the 'locked' state.
So what we do here is wait for 'node->next' to be set; it might still be NULL if the other cpu is between:
prev = xchg(lock->tail, node);
and:
prev->next = node;
Once we observe the next node, we call arch_mcs_spin_unlock_contended() on it, which sets its mcs_spinlock::locked and makes the new 'top of queue' drop out of arch_mcs_spin_lock_contended and spin on the 'locked' state as said above.
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
Quoted 13 lines · Peter Zijlstra
Quoted 11 lines · Konrad Rzeszutek Wilk
kernel/locking/mcs_spinlock.h line 20
Quoted 9 lines
Index:
linux-2.6/kernel/locking/mcs_spinlock.h
===================================================================kernel/locking/mcs_spinlock.h +1
-0@@ -17,6 +17,7 @@struct mcs_spinlock {struct mcs_spinlock *next;int locked;/* 1 if lock acquired */ +int count;This could use a comment.
Quoted 3 lines
like so?
int count; /* nesting count, see
qspinlock.c*/
/* nested level - in user, softirq, hard irq or nmi context. */ ?
Quoted 13 lines
Quoted 13 lines
kernel/locking/mcs_spinlock.h
Quoted 8 lines
kernel/locking/mcs_spinlock.h +6
-0+static inline u32 encode_tail (int cpu,int idx) +{ +u32 tail; + + tail = (cpu +1 ) << _Q_TAIL_CPU_OFFSET; + tail |= idx << _Q_TAIL_IDX_OFFSET;/* assume < 4 */ Should there an
ASSSERT (idx < 4)
Quoted 8 lines
Quoted 2 lines
just in case we screw up somehow (I can't figure out how, but that is partially why ASSERTS are added).
#ifdef CONFIG_DEBUG_SPINLOCK BUG_ON(idx > 3);#endif
might do, I suppose.
<nods>
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
+/**
+ *queue_spin_lock_slowpath- acquire the queue spinlock
+ * @lock: Pointer to queue spinlock structure
+ * @val: Current value of the queue spinlock 32-bit word
+ *
+ * (queue tail, lock bit)
Quoted 4 lines
Quoted 1 line
Except it is not a lock bit. It is a lock
uint8_t.It is indeed, although that's an accident of implementation. I could do s/bit// and not mention the entire storage angle at all?
I think giving as much details as possible is good.
What you said 'accident of implementation' is a could be woven in there?
Quoted 26 lines
Quoted 2 lines
Is the queue tail at this point the composite of 'cpu|idx'?
Yes, as per {en,de}code_tail() above.
Quoted 9 lines
Quoted 7 lines
+ *
+ * fast : slow : unlock + * : : + * uncontended (0,0) --:--> (0,1) --------------------------------:--> (*,0) + * : | ^--------. / : + * : v \ | : + * uncontended : (n,x) --+--> (n,0) | :So many CPUn come in right? Is 'n' for the number of CPUs?
Nope, 'n' for any one specific tail, in particular the first one to arrive. This is the 'uncontended queue' case as per the label, so we need a named value for the first, in order to distinguish between the state to the right (same tail, but unlocked) and the state below (different tail).
Quoted 5 lines
Quoted 4 lines
+ * queue : | ^--' | : + * : v | : + * contended : (*,x) --+--> (*,0) -----> (*,1) ---' : + * queue : ^--' :
Quoted 5 lines
Quoted 3 lines
And here um, what are the '*' for? Are they the four different types of handlers that can be nested? So task, sofitrq, hardisk, and nmi?
'*' as in wildcard, any tail, specifically not 'n'.
Ah, thank you for the explanation! Would it be possible to include that in the comment please?
Quoted 15 lines
Quoted 14 lines
kernel/locking/mcs_spinlock.h
Quoted 11 lines
kernel/locking/mcs_spinlock.h +10
-0+void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val) +{ +struct mcs_spinlock *prev, *next, *node; +u32 new, old, tail; +int idx; + +BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); + + node =this_cpu_ptr (&mcs_nodes[0 ]); + idx = node->count++;If this is the first time we enter this, wouldn't idx end up being 1?
Quoted 1 line
Nope, postfix ++ returns first and increments later.
<blushes> Yes it does.
Quoted 10 lines
Quoted 9 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +3
-0+ tail =encode_tail (smp_processor_id (), idx); + + node += idx;Meaning we end up skipping the 'mcs_nodes[0]' one altogether - even on the first 'level' (task, softirq, hardirq, nmi)? Won't that cause us to blow past the array when we are nested at the nmi handler?
Quoted 3 lines
Seeing how its all static storage, which is automagically initialized to 0, combined with the postfix ++ (as opposed to the prefix ++) we should be getting 0 here.
I've no idea what I was thinking, but thank you for setting me straight.
Quoted 21 lines
Quoted 15 lines
kernel/locking/mcs_spinlock.h
Quoted 9 lines
kernel/locking/mcs_spinlock.h +8
-0+ node->locked =0 ; + node->next =NULL ; + +/* +* trylock || xchg(lock, node) +* +* 0,0 -> 0,1 ; trylock +* p,x -> n,x ; prev = xchg(lock, node) I looked at that for 10 seconds and I was not sure what you meant. Is this related to the MCS document you had pointed to? It would help if you mention that the comments follow the document. (But they don't seem to)
Quoted 5 lines
I presume what you mean is that if we are the next after the lock-holder we need only to update the 'next' (or the composite value of
smp_processor_idx| idx) to point to us.As in, swap the 'L' with 'I' (looking at the doc)
Quoted 8 lines
They are the 'tail','lock' tuples, so this composite atomic operation completes either:
0,0 -> 0,1 -- we had no tail, not locked; into: no tail, locked.
OR
p,x -> n,x -- tail was p; into: tail is n; preserving locked.
Oh this is good!
Quoted 121 lines
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 5 lines
kernel/locking/mcs_spinlock.h +4
-0+*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val)Could you add a comment here, like this:
Quoted 10 lines
/*
* N.B. Initially 'val' will have some value (as we are called
* after the_Q_LOCKED_VALcould not be set byqueue_spin_lock).
* But on subsequent iterations, either the lock holder will
* decrement the val (queue_spin_unlock- to zero) and we
* needn't to record our status in the queue as we have set the
*Q_LOCKED_VAL(new) and are the lock holder. Or we are next
* in line and need to record our 'next' (aka,smp_processor_id()| idx)
* position. */
*/The idea was that:
0,0 -> 0,1
p,x -> n,xCompletely covers what this composite atomic does.
Quoted 39 lines
Quoted 39 lines
kernel/locking/mcs_spinlock.h +39
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; + +/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked); + } + +/* +* we're at the head of the waitqueue, wait for the owner to go away. +* +* *,x -> *,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); + +/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +4
-0+for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;You lost me here. If we are at the head of the queue, and the owner has called
queue_spin_unlock(hence made us get out of the 'val =atomic_read' loop, how can val != tail?Remember:
Quoted 1 line
kernel/locking/mcs_spinlock.h
Quoted 1 line
kernel/locking/mcs_spinlock.h +1
-0+ tail =encode_tail (smp_processor_id (), idx);So if value != tail, that means the tail pointer doesn't point to us anymore, another cpu/idx queued itself and is now last.
Quoted 4 lines
I suspect it has something to do with the comment, but I am still unsure what it means.
Could you help a bit in explaining it in English please?
(refer to the state diagram, if we count states
left->right,top->bottom, then this is: 5->2 or 7->8n,0 -> 0,1:
the lock is free and the tail points to the first queued; this means that unqueueing implies wiping the tail, at the same time, acquire the lock.
*,0 -> *,1:
the lock is free and the tail doesn't point to the first queued; this means that unqueueing doesn't touch the tail pointer but only sets the lock.
Quoted 20 lines
kernel/locking/mcs_spinlock.h
Quoted 12 lines
kernel/locking/mcs_spinlock.h +12
-0+ + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* contended path; wait for next, release. +*/ +if (new != _Q_LOCKED_VAL) {Hm, wouldn't it be just easier to do a 'goto restart' where restart label points at the first loop statement? Ah never mind - we have already inserted ourselves in the previous's node.
But that is confusing - we have done: "
prev->next= node;"
Quoted 8 lines
Quoted 3 lines
And then exited out of 'val =
atomic_read(&lock->val))' which suggests thatqueue_spin_unlockhas called us. How can we be contended again?We're not contended again; we're in the 'contended queued' case, which means that 'tail' didn't point to us anymore, in that case, we must kick our next node such that it will now drop out of
arch_mcs_spin_lock_contended()and goes wait on the 'locked' state.
<nods>
Quoted 6 lines
So what we do here is wait for '
node->next' to be set; it might still be NULL if the other cpu is between:prev = xchg(lock->tail, node);
Quoted 8 lines
and:
prev->next = node;Once we observe the next node, we call
arch_mcs_spin_unlock_contended()on it, which sets itsmcs_spinlock::lockedand makes the new 'top of queue' drop out ofarch_mcs_spin_lock_contendedand spin on the 'locked' state as said above.
Thank you for your detailed explanation!
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Peter ZijlstraQuoted 13 lines · Peter Zijlstra
Quoted 11 lines · Konrad Rzeszutek Wilk
kernel/locking/mcs_spinlock.h line 20
Quoted 9 lines
Index:
linux-2.6/kernel/locking/mcs_spinlock.h
===================================================================kernel/locking/mcs_spinlock.h +1
-0@@ -17,6 +17,7 @@struct mcs_spinlock {struct mcs_spinlock *next;int locked;/* 1 if lock acquired */ +int count;This could use a comment.
Quoted 3 lines
like so?
int count; /* nesting count, see
qspinlock.c*/
/* nested level - in user, softirq, hard irq or nmi context. */ ?
Quoted 13 lines
Quoted 13 lines
kernel/locking/mcs_spinlock.h
Quoted 8 lines
kernel/locking/mcs_spinlock.h +6
-0+static inline u32 encode_tail (int cpu,int idx) +{ +u32 tail; + + tail = (cpu +1 ) << _Q_TAIL_CPU_OFFSET; + tail |= idx << _Q_TAIL_IDX_OFFSET;/* assume < 4 */ Should there an
ASSSERT (idx < 4)
Quoted 8 lines
Quoted 2 lines
just in case we screw up somehow (I can't figure out how, but that is partially why ASSERTS are added).
#ifdef CONFIG_DEBUG_SPINLOCK BUG_ON(idx > 3);#endif
might do, I suppose.
<nods>
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
+/**
+ *queue_spin_lock_slowpath- acquire the queue spinlock
+ * @lock: Pointer to queue spinlock structure
+ * @val: Current value of the queue spinlock 32-bit word
+ *
+ * (queue tail, lock bit)
Quoted 4 lines
Quoted 1 line
Except it is not a lock bit. It is a lock
uint8_t.It is indeed, although that's an accident of implementation. I could do s/bit// and not mention the entire storage angle at all?
I think giving as much details as possible is good.
What you said 'accident of implementation' is a could be woven in there?
Quoted 26 lines
Quoted 2 lines
Is the queue tail at this point the composite of 'cpu|idx'?
Yes, as per {en,de}code_tail() above.
Quoted 9 lines
Quoted 7 lines
+ *
+ * fast : slow : unlock + * : : + * uncontended (0,0) --:--> (0,1) --------------------------------:--> (*,0) + * : | ^--------. / : + * : v \ | : + * uncontended : (n,x) --+--> (n,0) | :So many CPUn come in right? Is 'n' for the number of CPUs?
Nope, 'n' for any one specific tail, in particular the first one to arrive. This is the 'uncontended queue' case as per the label, so we need a named value for the first, in order to distinguish between the state to the right (same tail, but unlocked) and the state below (different tail).
Quoted 5 lines
Quoted 4 lines
+ * queue : | ^--' | : + * : v | : + * contended : (*,x) --+--> (*,0) -----> (*,1) ---' : + * queue : ^--' :
Quoted 5 lines
Quoted 3 lines
And here um, what are the '*' for? Are they the four different types of handlers that can be nested? So task, sofitrq, hardisk, and nmi?
'*' as in wildcard, any tail, specifically not 'n'.
Ah, thank you for the explanation! Would it be possible to include that in the comment please?
Quoted 15 lines
Quoted 14 lines
kernel/locking/mcs_spinlock.h
Quoted 11 lines
kernel/locking/mcs_spinlock.h +10
-0+void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val) +{ +struct mcs_spinlock *prev, *next, *node; +u32 new, old, tail; +int idx; + +BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); + + node =this_cpu_ptr (&mcs_nodes[0 ]); + idx = node->count++;If this is the first time we enter this, wouldn't idx end up being 1?
Quoted 1 line
Nope, postfix ++ returns first and increments later.
<blushes> Yes it does.
Quoted 10 lines
Quoted 9 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +3
-0+ tail =encode_tail (smp_processor_id (), idx); + + node += idx;Meaning we end up skipping the 'mcs_nodes[0]' one altogether - even on the first 'level' (task, softirq, hardirq, nmi)? Won't that cause us to blow past the array when we are nested at the nmi handler?
Quoted 3 lines
Seeing how its all static storage, which is automagically initialized to 0, combined with the postfix ++ (as opposed to the prefix ++) we should be getting 0 here.
I've no idea what I was thinking, but thank you for setting me straight.
Quoted 21 lines
Quoted 15 lines
kernel/locking/mcs_spinlock.h
Quoted 9 lines
kernel/locking/mcs_spinlock.h +8
-0+ node->locked =0 ; + node->next =NULL ; + +/* +* trylock || xchg(lock, node) +* +* 0,0 -> 0,1 ; trylock +* p,x -> n,x ; prev = xchg(lock, node) I looked at that for 10 seconds and I was not sure what you meant. Is this related to the MCS document you had pointed to? It would help if you mention that the comments follow the document. (But they don't seem to)
Quoted 5 lines
I presume what you mean is that if we are the next after the lock-holder we need only to update the 'next' (or the composite value of
smp_processor_idx| idx) to point to us.As in, swap the 'L' with 'I' (looking at the doc)
Quoted 8 lines
They are the 'tail','lock' tuples, so this composite atomic operation completes either:
0,0 -> 0,1 -- we had no tail, not locked; into: no tail, locked.
OR
p,x -> n,x -- tail was p; into: tail is n; preserving locked.
Oh this is good!
Quoted 121 lines
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 5 lines
kernel/locking/mcs_spinlock.h +4
-0+*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val)Could you add a comment here, like this:
Quoted 10 lines
/*
* N.B. Initially 'val' will have some value (as we are called
* after the_Q_LOCKED_VALcould not be set byqueue_spin_lock).
* But on subsequent iterations, either the lock holder will
* decrement the val (queue_spin_unlock- to zero) and we
* needn't to record our status in the queue as we have set the
*Q_LOCKED_VAL(new) and are the lock holder. Or we are next
* in line and need to record our 'next' (aka,smp_processor_id()| idx)
* position. */
*/The idea was that:
0,0 -> 0,1
p,x -> n,xCompletely covers what this composite atomic does.
Quoted 39 lines
Quoted 39 lines
kernel/locking/mcs_spinlock.h +39
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; + +/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked); + } + +/* +* we're at the head of the waitqueue, wait for the owner to go away. +* +* *,x -> *,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); + +/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +4
-0+for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;You lost me here. If we are at the head of the queue, and the owner has called
queue_spin_unlock(hence made us get out of the 'val =atomic_read' loop, how can val != tail?Remember:
Quoted 1 line
kernel/locking/mcs_spinlock.h
Quoted 1 line
kernel/locking/mcs_spinlock.h +1
-0+ tail =encode_tail (smp_processor_id (), idx);So if value != tail, that means the tail pointer doesn't point to us anymore, another cpu/idx queued itself and is now last.
Quoted 4 lines
I suspect it has something to do with the comment, but I am still unsure what it means.
Could you help a bit in explaining it in English please?
(refer to the state diagram, if we count states
left->right,top->bottom, then this is: 5->2 or 7->8n,0 -> 0,1:
the lock is free and the tail points to the first queued; this means that unqueueing implies wiping the tail, at the same time, acquire the lock.
*,0 -> *,1:
the lock is free and the tail doesn't point to the first queued; this means that unqueueing doesn't touch the tail pointer but only sets the lock.
Quoted 20 lines
kernel/locking/mcs_spinlock.h
Quoted 12 lines
kernel/locking/mcs_spinlock.h +12
-0+ + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* contended path; wait for next, release. +*/ +if (new != _Q_LOCKED_VAL) {Hm, wouldn't it be just easier to do a 'goto restart' where restart label points at the first loop statement? Ah never mind - we have already inserted ourselves in the previous's node.
But that is confusing - we have done: "
prev->next= node;"
Quoted 8 lines
Quoted 3 lines
And then exited out of 'val =
atomic_read(&lock->val))' which suggests thatqueue_spin_unlockhas called us. How can we be contended again?We're not contended again; we're in the 'contended queued' case, which means that 'tail' didn't point to us anymore, in that case, we must kick our next node such that it will now drop out of
arch_mcs_spin_lock_contended()and goes wait on the 'locked' state.
<nods>
Quoted 6 lines
So what we do here is wait for '
node->next' to be set; it might still be NULL if the other cpu is between:prev = xchg(lock->tail, node);
Quoted 8 lines
and:
prev->next = node;Once we observe the next node, we call
arch_mcs_spin_unlock_contended()on it, which sets itsmcs_spinlock::lockedand makes the new 'top of queue' drop out ofarch_mcs_spin_lock_contendedand spin on the 'locked' state as said above.
Thank you for your detailed explanation!
Peter Zijlstra
maintainerRe: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Konrad Rzeszutek WilkQuoted 11 lines · Konrad Rzeszutek Wilk
kernel/locking/mcs_spinlock.h line 20
Quoted 9 lines
Index:
linux-2.6/kernel/locking/mcs_spinlock.h
===================================================================kernel/locking/mcs_spinlock.h +1
-0@@ -17,6 +17,7 @@struct mcs_spinlock {struct mcs_spinlock *next;int locked;/* 1 if lock acquired */ +int count;This could use a comment.
like so?
int count; /* nesting count, see qspinlock.c */
Quoted 9 lines
kernel/locking/mcs_spinlock.h
Quoted 6 lines
kernel/locking/mcs_spinlock.h +6
-0+static inline u32 encode_tail (int cpu,int idx) +{ +u32 tail; + + tail = (cpu +1 ) << _Q_TAIL_CPU_OFFSET; + tail |= idx << _Q_TAIL_IDX_OFFSET;/* assume < 4 */ Should there an
Quoted 4 lines
ASSSERT (idx < 4)
just in case we screw up somehow (I can't figure out how, but that is partially why ASSERTS are added).
#ifdef CONFIG_DEBUG_SPINLOCK BUG_ON(idx > 3);
#endif
might do, I suppose.
Quoted 8 lines
Quoted 6 lines
+/**
+ *queue_spin_lock_slowpath- acquire the queue spinlock
+ * @lock: Pointer to queue spinlock structure
+ * @val: Current value of the queue spinlock 32-bit word
+ *
+ * (queue tail, lock bit)Except it is not a lock bit. It is a lock
uint8_t.
It is indeed, although that's an accident of implementation. I could do s/bit// and not mention the entire storage angle at all?
Quoted 1 line
Is the queue tail at this point the composite of 'cpu|idx'?
Yes, as per {en,de}code_tail() above.
Quoted 9 lines
Quoted 7 lines
+ *
+ * fast : slow : unlock + * : : + * uncontended (0,0) --:--> (0,1) --------------------------------:--> (*,0) + * : | ^--------. / : + * : v \ | : + * uncontended : (n,x) --+--> (n,0) | :So many CPUn come in right? Is 'n' for the number of CPUs?
Nope, 'n' for any one specific tail, in particular the first one to arrive. This is the 'uncontended queue' case as per the label, so we need a named value for the first, in order to distinguish between the state to the right (same tail, but unlocked) and the state below (different tail).
Quoted 8 lines
Quoted 4 lines
+ * queue : | ^--' | : + * : v | : + * contended : (*,x) --+--> (*,0) -----> (*,1) ---' : + * queue : ^--' :And here um, what are the '*' for? Are they the four different types of handlers that can be nested? So task, sofitrq, hardisk, and nmi?
'*' as in wildcard, any tail, specifically not 'n'.
Quoted 5 lines
Quoted 5 lines
kernel/locking/mcs_spinlock.h +5
-0+void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val) +{ +struct mcs_spinlock *prev, *next, *node; +u32 new, old, tail; +int idx;
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 5 lines
kernel/locking/mcs_spinlock.h +5
-0+ +BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); + + node =this_cpu_ptr (&mcs_nodes[0 ]); + idx = node->count++;If this is the first time we enter this, wouldn't idx end up being 1?
Nope, postfix ++ returns first and increments later.
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 3 lines
kernel/locking/mcs_spinlock.h +3
-0+ tail =encode_tail (smp_processor_id (), idx); + + node += idx;Meaning we end up skipping the 'mcs_nodes[0]' one altogether - even on the first 'level' (task, softirq, hardirq, nmi)? Won't that cause us to blow past the array when we are nested at the nmi handler?
Seeing how its all static storage, which is automagically initialized to 0, combined with the postfix ++ (as opposed to the prefix ++) we should be getting 0 here.
Quoted 14 lines
kernel/locking/mcs_spinlock.h
Quoted 8 lines
kernel/locking/mcs_spinlock.h +8
-0+ node->locked =0 ; + node->next =NULL ; + +/* +* trylock || xchg(lock, node) +* +* 0,0 -> 0,1 ; trylock +* p,x -> n,x ; prev = xchg(lock, node) I looked at that for 10 seconds and I was not sure what you meant. Is this related to the MCS document you had pointed to? It would help if you mention that the comments follow the document. (But they don't seem to)
Quoted 5 lines
I presume what you mean is that if we are the next after the lock-holder we need only to update the 'next' (or the composite value of
smp_processor_idx| idx) to point to us.As in, swap the 'L' with 'I' (looking at the doc)
They are the 'tail','lock' tuples, so this composite atomic operation completes either:
0,0 -> 0,1 -- we had no tail, not locked; into: no tail, locked.
OR
p,x -> n,x -- tail was p; into: tail is n; preserving locked.
Quoted 7 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +4
-0+*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val)Could you add a comment here, like this:
Quoted 10 lines
/*
* N.B. Initially 'val' will have some value (as we are called
* after the_Q_LOCKED_VALcould not be set byqueue_spin_lock).
* But on subsequent iterations, either the lock holder will
* decrement the val (queue_spin_unlock- to zero) and we
* needn't to record our status in the queue as we have set the
*Q_LOCKED_VAL(new) and are the lock holder. Or we are next
* in line and need to record our 'next' (aka,smp_processor_id()| idx)
* position. */
*/
The idea was that:
0,0 -> 0,1
p,x -> n,x
Completely covers what this composite atomic does.
Quoted 39 lines
Quoted 39 lines
kernel/locking/mcs_spinlock.h +39
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; + +/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked); + } + +/* +* we're at the head of the waitqueue, wait for the owner to go away. +* +* *,x -> *,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); + +/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +4
-0+for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;You lost me here. If we are at the head of the queue, and the owner has called
queue_spin_unlock(hence made us get out of the 'val =atomic_read' loop, how can val != tail?
Remember:
Quoted 1 line
kernel/locking/mcs_spinlock.h
Quoted 1 line
kernel/locking/mcs_spinlock.h +1
-0+ tail =encode_tail (smp_processor_id (), idx);
So if value != tail, that means the tail pointer doesn't point to us anymore, another cpu/idx queued itself and is now last.
Quoted 4 lines
I suspect it has something to do with the comment, but I am still unsure what it means.
Could you help a bit in explaining it in English please?
(refer to the state diagram, if we count states left->right, top->bottom, then this is: 5->2 or 7->8
n,0 -> 0,1:
the lock is free and the tail points to the first queued; this means that unqueueing implies wiping the tail, at the same time, acquire the lock.
*,0 -> *,1:
the lock is free and the tail doesn't point to the first queued; this means that unqueueing doesn't touch the tail pointer but only sets the lock.
Quoted 18 lines
kernel/locking/mcs_spinlock.h
Quoted 12 lines
kernel/locking/mcs_spinlock.h +12
-0+ + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* contended path; wait for next, release. +*/ +if (new != _Q_LOCKED_VAL) {Hm, wouldn't it be just easier to do a 'goto restart' where restart label points at the first loop statement? Ah never mind - we have already inserted ourselves in the previous's node.
Quoted 5 lines
But that is confusing - we have done: "
prev->next= node;"And then exited out of 'val =
atomic_read(&lock->val))' which suggests thatqueue_spin_unlockhas called us. How can we be contended again?
We're not contended again; we're in the 'contended queued' case, which means that 'tail' didn't point to us anymore, in that case, we must kick our next node such that it will now drop out of arch_mcs_spin_lock_contended() and goes wait on the 'locked' state.
So what we do here is wait for 'node->next' to be set; it might still be NULL if the other cpu is between:
prev = xchg(lock->tail, node);
and:
prev->next = node;
Once we observe the next node, we call arch_mcs_spin_unlock_contended() on it, which sets its mcs_spinlock::locked and makes the new 'top of queue' drop out of arch_mcs_spin_lock_contended and spin on the 'locked' state as said above.
Peter Zijlstra
maintainerRe: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Konrad Rzeszutek WilkQuoted 11 lines · Konrad Rzeszutek Wilk
kernel/locking/mcs_spinlock.h line 20
Quoted 9 lines
Index:
linux-2.6/kernel/locking/mcs_spinlock.h
===================================================================kernel/locking/mcs_spinlock.h +1
-0@@ -17,6 +17,7 @@struct mcs_spinlock {struct mcs_spinlock *next;int locked;/* 1 if lock acquired */ +int count;This could use a comment.
like so?
int count; /* nesting count, see qspinlock.c */
Quoted 9 lines
kernel/locking/mcs_spinlock.h
Quoted 6 lines
kernel/locking/mcs_spinlock.h +6
-0+static inline u32 encode_tail (int cpu,int idx) +{ +u32 tail; + + tail = (cpu +1 ) << _Q_TAIL_CPU_OFFSET; + tail |= idx << _Q_TAIL_IDX_OFFSET;/* assume < 4 */ Should there an
Quoted 4 lines
ASSSERT (idx < 4)
just in case we screw up somehow (I can't figure out how, but that is partially why ASSERTS are added).
#ifdef CONFIG_DEBUG_SPINLOCK BUG_ON(idx > 3);
#endif
might do, I suppose.
Quoted 8 lines
Quoted 6 lines
+/**
+ *queue_spin_lock_slowpath- acquire the queue spinlock
+ * @lock: Pointer to queue spinlock structure
+ * @val: Current value of the queue spinlock 32-bit word
+ *
+ * (queue tail, lock bit)Except it is not a lock bit. It is a lock
uint8_t.
It is indeed, although that's an accident of implementation. I could do s/bit// and not mention the entire storage angle at all?
Quoted 1 line
Is the queue tail at this point the composite of 'cpu|idx'?
Yes, as per {en,de}code_tail() above.
Quoted 9 lines
Quoted 7 lines
+ *
+ * fast : slow : unlock + * : : + * uncontended (0,0) --:--> (0,1) --------------------------------:--> (*,0) + * : | ^--------. / : + * : v \ | : + * uncontended : (n,x) --+--> (n,0) | :So many CPUn come in right? Is 'n' for the number of CPUs?
Nope, 'n' for any one specific tail, in particular the first one to arrive. This is the 'uncontended queue' case as per the label, so we need a named value for the first, in order to distinguish between the state to the right (same tail, but unlocked) and the state below (different tail).
Quoted 8 lines
Quoted 4 lines
+ * queue : | ^--' | : + * : v | : + * contended : (*,x) --+--> (*,0) -----> (*,1) ---' : + * queue : ^--' :And here um, what are the '*' for? Are they the four different types of handlers that can be nested? So task, sofitrq, hardisk, and nmi?
'*' as in wildcard, any tail, specifically not 'n'.
Quoted 5 lines
Quoted 5 lines
kernel/locking/mcs_spinlock.h +5
-0+void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val) +{ +struct mcs_spinlock *prev, *next, *node; +u32 new, old, tail; +int idx;
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 5 lines
kernel/locking/mcs_spinlock.h +5
-0+ +BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); + + node =this_cpu_ptr (&mcs_nodes[0 ]); + idx = node->count++;If this is the first time we enter this, wouldn't idx end up being 1?
Nope, postfix ++ returns first and increments later.
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 3 lines
kernel/locking/mcs_spinlock.h +3
-0+ tail =encode_tail (smp_processor_id (), idx); + + node += idx;Meaning we end up skipping the 'mcs_nodes[0]' one altogether - even on the first 'level' (task, softirq, hardirq, nmi)? Won't that cause us to blow past the array when we are nested at the nmi handler?
Seeing how its all static storage, which is automagically initialized to 0, combined with the postfix ++ (as opposed to the prefix ++) we should be getting 0 here.
Quoted 14 lines
kernel/locking/mcs_spinlock.h
Quoted 8 lines
kernel/locking/mcs_spinlock.h +8
-0+ node->locked =0 ; + node->next =NULL ; + +/* +* trylock || xchg(lock, node) +* +* 0,0 -> 0,1 ; trylock +* p,x -> n,x ; prev = xchg(lock, node) I looked at that for 10 seconds and I was not sure what you meant. Is this related to the MCS document you had pointed to? It would help if you mention that the comments follow the document. (But they don't seem to)
Quoted 5 lines
I presume what you mean is that if we are the next after the lock-holder we need only to update the 'next' (or the composite value of
smp_processor_idx| idx) to point to us.As in, swap the 'L' with 'I' (looking at the doc)
They are the 'tail','lock' tuples, so this composite atomic operation completes either:
0,0 -> 0,1 -- we had no tail, not locked; into: no tail, locked.
OR
p,x -> n,x -- tail was p; into: tail is n; preserving locked.
Quoted 7 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +4
-0+*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val)Could you add a comment here, like this:
Quoted 10 lines
/*
* N.B. Initially 'val' will have some value (as we are called
* after the_Q_LOCKED_VALcould not be set byqueue_spin_lock).
* But on subsequent iterations, either the lock holder will
* decrement the val (queue_spin_unlock- to zero) and we
* needn't to record our status in the queue as we have set the
*Q_LOCKED_VAL(new) and are the lock holder. Or we are next
* in line and need to record our 'next' (aka,smp_processor_id()| idx)
* position. */
*/
The idea was that:
0,0 -> 0,1
p,x -> n,x
Completely covers what this composite atomic does.
Quoted 39 lines
Quoted 39 lines
kernel/locking/mcs_spinlock.h +39
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; + +/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked); + } + +/* +* we're at the head of the waitqueue, wait for the owner to go away. +* +* *,x -> *,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); + +/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/
Quoted 8 lines
kernel/locking/mcs_spinlock.h
Quoted 4 lines
kernel/locking/mcs_spinlock.h +4
-0+for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;You lost me here. If we are at the head of the queue, and the owner has called
queue_spin_unlock(hence made us get out of the 'val =atomic_read' loop, how can val != tail?
Remember:
Quoted 1 line
kernel/locking/mcs_spinlock.h
Quoted 1 line
kernel/locking/mcs_spinlock.h +1
-0+ tail =encode_tail (smp_processor_id (), idx);
So if value != tail, that means the tail pointer doesn't point to us anymore, another cpu/idx queued itself and is now last.
Quoted 4 lines
I suspect it has something to do with the comment, but I am still unsure what it means.
Could you help a bit in explaining it in English please?
(refer to the state diagram, if we count states left->right, top->bottom, then this is: 5->2 or 7->8
n,0 -> 0,1:
the lock is free and the tail points to the first queued; this means that unqueueing implies wiping the tail, at the same time, acquire the lock.
*,0 -> *,1:
the lock is free and the tail doesn't point to the first queued; this means that unqueueing doesn't touch the tail pointer but only sets the lock.
Quoted 18 lines
kernel/locking/mcs_spinlock.h
Quoted 12 lines
kernel/locking/mcs_spinlock.h +12
-0+ + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* contended path; wait for next, release. +*/ +if (new != _Q_LOCKED_VAL) {Hm, wouldn't it be just easier to do a 'goto restart' where restart label points at the first loop statement? Ah never mind - we have already inserted ourselves in the previous's node.
Quoted 5 lines
But that is confusing - we have done: "
prev->next= node;"And then exited out of 'val =
atomic_read(&lock->val))' which suggests thatqueue_spin_unlockhas called us. How can we be contended again?
We're not contended again; we're in the 'contended queued' case, which means that 'tail' didn't point to us anymore, in that case, we must kick our next node such that it will now drop out of arch_mcs_spin_lock_contended() and goes wait on the 'locked' state.
So what we do here is wait for 'node->next' to be set; it might still be NULL if the other cpu is between:
prev = xchg(lock->tail, node);
and:
prev->next = node;
Once we observe the next node, we call arch_mcs_spin_unlock_contended() on it, which sets its mcs_spinlock::locked and makes the new 'top of queue' drop out of arch_mcs_spin_lock_contended and spin on the 'locked' state as said above.
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Peter ZijlstraQuoted 28 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
This patch introduces a new generic queue spinlock implementation that can serve as an alternative to the default ticket spinlock. Compared with the ticket spinlock, this queue spinlock should be almost as fair as the ticket spinlock. It has about the same speed in single-thread and it can be much faster in high contention situations especially when the spinlock is embedded within the data structure to be protected.
Only in light to moderate contention where the average queue depth is around 1-3 will this queue spinlock be potentially a bit slower due to the higher slowpath overhead.
This queue spinlock is especially suit to NUMA machines with a large number of cores as the chance of spinlock contention is much higher in those machines. The cost of contention is also higher because of slower inter-node memory traffic.
Due to the fact that spinlocks are acquired with preemption disabled, the process will not be migrated to another CPU while it is trying to get a spinlock. Ignoring interrupt handling, a CPU can only be contending in one spinlock at any one time. Counting soft IRQ, hard IRQ and NMI, a CPU can only have a maximum of 4 concurrent lock waiting activities. By allocating a set of per-cpu queue nodes and used them to form a waiting queue, we can encode the queue node address into a much smaller 24-bit size (including CPU number and queue node index) leaving one byte for the lock.
Quoted 6 lines
Please note that the queue node is only needed when waiting for the lock. Once the lock is acquired, the queue node can be released to be used later.
Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>
Thank you for the repost. I have some questions about the implementation that hopefully will be easy to answer and said answers I hope can be added in the code to enlighten other folks.
See below.
.. snip..
kernel/locking/mcs_spinlock.h line 20
Quoted 9 lines
Index:
linux-2.6/kernel/locking/mcs_spinlock.h
===================================================================kernel/locking/mcs_spinlock.h +1
-0@@ -17,6 +17,7 @@struct mcs_spinlock {struct mcs_spinlock *next;int locked;/* 1 if lock acquired */ +int count;
This could use a comment.
Quoted 71 lines
kernel/locking/mcs_spinlock.h +0
-0};#ifndef arch_mcs_spin_lock_contendedIndex:
linux-2.6/kernel/locking/qspinlock.c
===================================================================kernel/locking/qspinlock.c new file +63
-0@@ -0,0 +1,197 @@ +/* +* Queue spinlock +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* (C) Copyright 2013-2014 Hewlett-Packard Development Company, L.P. +* +* Authors: Waiman Long <[email protected]> +* Peter Zijlstra <[email protected]> +*/ +#include <linux/smp.h> +#include <linux/bug.h> +#include <linux/cpumask.h> +#include <linux/percpu.h> +#include <linux/hardirq.h> +#include <linux/mutex.h> +#include <asm/qspinlock.h> + +/* +* The basic principle of a queue-based spinlock can best be understood +* by studying a classic queue-based spinlock implementation called the +* MCS lock. The paper below provides a good description for this kind +* of lock. +* +* http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf +* +* This queue spinlock implementation is based on the MCS lock, however to make +* it fit the 4 bytes we assume spinlock_t to be, and preserve its existing +* API, we must modify it some. +* +* In particular; where the traditional MCS lock consists of a tail pointer +* (8 bytes) and needs the next pointer (another 8 bytes) of its own node to +* unlock the next pending (next->locked), we compress both these: {tail, +* next->locked} into a single u32 value. +* +* Since a spinlock disables recursion of its own context and there is a limit +* to the contexts that can nest; namely: task, softirq, hardirq, nmi, we can +* encode the tail as and index indicating this context and a cpu number. +* +* We can further change the first spinner to spin on a bit in the lock word +* instead of its node; whereby avoiding the need to carry a node from lock to +* unlock, and preserving API. +*/ + +#include "mcs_spinlock.h" + +/* +* Per-CPU queue node structures; we can never have more than 4 nested +* contexts: task, softirq, hardirq, nmi. +* +* Exactly fits one cacheline. +*/ +static DEFINE_PER_CPU_ALIGNED(struct mcs_spinlock, mcs_nodes[4]); + +/*
kernel/locking/qspinlock.c line 73
Quoted 10 lines
kernel/locking/qspinlock.c new file +10
-0+* We must be able to distinguish between no-tail and the tail at 0:0, +* therefore increment the cpu number by one. +*/ + +static inline u32 encode_tail (int cpu,int idx) +{ +u32 tail; + + tail = (cpu +1 ) << _Q_TAIL_CPU_OFFSET; + tail |= idx << _Q_TAIL_IDX_OFFSET;/* assume < 4 */
Should there an
ASSSERT (idx < 4)
just in case we screw up somehow (I can't figure out how, but that is partially why ASSERTS are added).
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+ +return tail; +} + +static inline struct mcs_spinlock *decode_tail (u32 tail) +{ +int cpu = (tail >> _Q_TAIL_CPU_OFFSET) -1 ; +int idx = (tail & _Q_TAIL_IDX_MASK) >> _Q_TAIL_IDX_OFFSET;
kernel/locking/qspinlock.c line 91
Quoted 10 lines
kernel/locking/qspinlock.c new file +10
-0+ +return per_cpu_ptr (&mcs_nodes[idx], cpu); +} + +/** +* queue_spin_lock_slowpath - acquire the queue spinlock +* @lock: Pointer to queue spinlock structure +* @val: Current value of the queue spinlock 32-bit word +* +* (queue tail, lock bit)
Except it is not a lock bit. It is a lock uint8_t.
Is the queue tail at this point the composite of 'cpu|idx'?
kernel/locking/qspinlock.c line 98
Quoted 7 lines
kernel/locking/qspinlock.c new file +7
-0+* +* fast : slow : unlock +* : : +* uncontended (0,0) --:--> (0,1) --------------------------------:--> (*,0) +* : | ^--------. / : +* : v \ | : +* uncontended : (n,x) --+--> (n,0) | :
So many CPUn come in right? Is 'n' for the number of CPUs?
kernel/locking/qspinlock.c line 102
Quoted 4 lines
kernel/locking/qspinlock.c new file +4
-0+* queue : | ^--' | : +* : v | : +* contended : (*,x) --+--> (*,0) -----> (*,1) ---' : +* queue : ^--' :
And here um, what are the '*' for? Are they the four different types of handlers that can be nested? So task, sofitrq, hardisk, and nmi?
kernel/locking/qspinlock.c line 114
Quoted 12 lines
kernel/locking/qspinlock.c new file +12
-0+* +*/ +void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val) +{ +struct mcs_spinlock *prev, *next, *node; +u32 new, old, tail; +int idx; + +BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); + + node =this_cpu_ptr (&mcs_nodes[0 ]); + idx = node->count++;
If this is the first time we enter this, wouldn't idx end up being 1?
kernel/locking/qspinlock.c line 117
Quoted 3 lines
kernel/locking/qspinlock.c new file +3
-0+ tail =encode_tail (smp_processor_id (), idx); + + node += idx;
Meaning we end up skipping the 'mcs_nodes[0]' one altogether - even on the first 'level' (task, softirq, hardirq, nmi)? Won't that cause us to blow past the array when we are nested at the nmi handler?
kernel/locking/qspinlock.c line 125
Quoted 8 lines
kernel/locking/qspinlock.c new file +8
-0+ node->locked =0 ; + node->next =NULL ; + +/* +* trylock || xchg(lock, node) +* +* 0,0 -> 0,1 ; trylock +* p,x -> n,x ; prev = xchg(lock, node)
I looked at that for 10 seconds and I was not sure what you meant. Is this related to the MCS document you had pointed to? It would help if you mention that the comments follow the document. (But they don't seem to)
I presume what you mean is that if we are the next after the lock-holder we need only to update the 'next' (or the composite value of smp_processor_idx | idx) to point to us.
As in, swap the 'L' with 'I' (looking at the doc)
kernel/locking/qspinlock.c line 129
Quoted 4 lines
kernel/locking/qspinlock.c new file +4
-0+*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val)
Could you add a comment here, like this:
/*
* N.B. Initially 'val' will have some value (as we are called
* after the _Q_LOCKED_VAL could not be set by queue_spin_lock).
* But on subsequent iterations, either the lock holder will
* decrement the val (queue_spin_unlock - to zero) and we
* needn't to record our status in the queue as we have set the
* Q_LOCKED_VAL (new) and are the lock holder. Or we are next
* in line and need to record our 'next' (aka, smp_processor_id() | idx)
* position. */
*/
Quoted 33 lines
kernel/locking/qspinlock.c new file +33
-0+ new = tail | (val & _Q_LOCKED_MASK); + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock; forget about queueing. +*/ +if (new == _Q_LOCKED_VAL) + goto release; + +/* +* if there was a previous node; link it and wait. +*/ +if (old & ~_Q_LOCKED_MASK) { + prev =decode_tail (old); +ACCESS_ONCE (prev->next) = node; + +arch_mcs_spin_lock_contended (&node->locked); + } + +/* +* we're at the head of the waitqueue, wait for the owner to go away. +* +* *,x -> *,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); +
kernel/locking/qspinlock.c line 172
Quoted 10 lines
kernel/locking/qspinlock.c new file +10
-0+/* +* claim the lock: +* +* n,0 -> 0,1 : lock, uncontended +* *,0 -> *,1 : lock, contended +*/ +for (;;) { + new = _Q_LOCKED_VAL; +if (val != tail) + new |= val;
You lost me here. If we are at the head of the queue, and the owner has called queue_spin_unlock (hence made us get out of the 'val = atomic_read' loop, how can val != tail?
I suspect it has something to do with the comment, but I am still unsure what it means.
Could you help a bit in explaining it in English please?
kernel/locking/qspinlock.c line 184
Quoted 12 lines
kernel/locking/qspinlock.c new file +12
-0+ + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* contended path; wait for next, release. +*/ +if (new != _Q_LOCKED_VAL) {
Hm, wouldn't it be just easier to do a 'goto restart' where restart label points at the first loop statement? Ah never mind - we have already inserted ourselves in the previous's node.
But that is confusing - we have done: "prev->next = node;"
And then exited out of 'val = atomic_read(&lock->val))' which suggests that queue_spin_unlock has called us. How can we be contended again?
Thanks!
Quoted 15 lines
kernel/locking/qspinlock.c new file +13
-0+while (!(next =ACCESS_ONCE (node->next))) +cpu_relax (); + +arch_mcs_spin_unlock_contended (&next->locked); + } + +release: +/* +* release the node +*/ +this_cpu_dec (mcs_nodes[0 ].count); +} +EXPORT_SYMBOL (queue_spin_lock_slowpath);
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Peter ZijlstraQuoted 15 lines
+ * The basic principle of a queue-based spinlock can best be understood
+ * by studying a classic queue-based spinlock implementation called the
+ * MCS lock. The paper below provides a good description for this kind
+ * of lock.
+ *
+ * http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf
+ *
+ * This queue spinlock implementation is based on the MCS lock, however to make
+ * it fit the 4 bytes we assumespinlock_tto be, and preserve its existing
+ * API, we must modify it some.
+ *
+ * In particular; where the traditional MCS lock consists of a tail pointer
+ * (8 bytes) and needs the next pointer (another 8 bytes) of its own node to
+ * unlock the next pending (next->locked), we compress both these: {tail,
+ *next->locked} into a single u32 value.
Quoted 8 lines
+ *
+ * Since a spinlock disables recursion of its own context and there is a limit
+ * to the contexts that can nest; namely: task, softirq, hardirq, nmi, we can
+ * encode the tail as and index indicating this context and a cpu number.
+ *
+ * We can further change the first spinner to spin on a bit in the lock word
+ * instead of its node; whereby avoiding the need to carry a node from lock to
+ * unlock, and preserving API.
You also made changes (compared to the MCS) in that the unlock path is not spinning waiting for the successor and that the job of passing the lock is not done in the unlock path either.
Instead all of that is now done in the path of the lock acquirer logic.
Could you update the comment to say that please?
Thanks.
Peter Zijlstra
maintainerRe: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
Quoted 24 lines · Konrad Rzeszutek Wilk
Quoted 15 lines
+ * The basic principle of a queue-based spinlock can best be understood
+ * by studying a classic queue-based spinlock implementation called the
+ * MCS lock. The paper below provides a good description for this kind
+ * of lock.
+ *
+ * http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf
+ *
+ * This queue spinlock implementation is based on the MCS lock, however to make
+ * it fit the 4 bytes we assumespinlock_tto be, and preserve its existing
+ * API, we must modify it some.
+ *
+ * In particular; where the traditional MCS lock consists of a tail pointer
+ * (8 bytes) and needs the next pointer (another 8 bytes) of its own node to
+ * unlock the next pending (next->locked), we compress both these: {tail,
+ *next->locked} into a single u32 value.Quoted 8 lines
+ *
+ * Since a spinlock disables recursion of its own context and there is a limit
+ * to the contexts that can nest; namely: task, softirq, hardirq, nmi, we can
+ * encode the tail as and index indicating this context and a cpu number.
+ *
+ * We can further change the first spinner to spin on a bit in the lock word
+ * instead of its node; whereby avoiding the need to carry a node from lock to
+ * unlock, and preserving API.
Quoted 7 lines
You also made changes (compared to the MCS) in that the unlock path is not spinning waiting for the successor and that the job of passing the lock is not done in the unlock path either.
Instead all of that is now done in the path of the lock acquirer logic.
Could you update the comment to say that please?
I _think_ I know what you mean.. So that is actually implied by the last paragraph, but I suppose I can make it explicit; something like:
*
* Another way to look at it is:
*
* lock(tail,locked)
* struct mcs_spinlock node; * mcs_spin_lock(tail, &node); * test-and-set locked; * mcs_spin_unlock(tail, &node);
*
* unlock(tail,locked)
* clear locked
*
* Where we have compressed (tail,locked) into a single u32 word.
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
Quoted 29 lines · Peter Zijlstra
Quoted 28 lines · Konrad Rzeszutek Wilk
Quoted 15 lines
+ * The basic principle of a queue-based spinlock can best be understood
+ * by studying a classic queue-based spinlock implementation called the
+ * MCS lock. The paper below provides a good description for this kind
+ * of lock.
+ *
+ * http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf
+ *
+ * This queue spinlock implementation is based on the MCS lock, however to make
+ * it fit the 4 bytes we assumespinlock_tto be, and preserve its existing
+ * API, we must modify it some.
+ *
+ * In particular; where the traditional MCS lock consists of a tail pointer
+ * (8 bytes) and needs the next pointer (another 8 bytes) of its own node to
+ * unlock the next pending (next->locked), we compress both these: {tail,
+ *next->locked} into a single u32 value.Quoted 8 lines
+ *
+ * Since a spinlock disables recursion of its own context and there is a limit
+ * to the contexts that can nest; namely: task, softirq, hardirq, nmi, we can
+ * encode the tail as and index indicating this context and a cpu number.
+ *
+ * We can further change the first spinner to spin on a bit in the lock word
+ * instead of its node; whereby avoiding the need to carry a node from lock to
+ * unlock, and preserving API.You also made changes (compared to the MCS) in that the unlock path is not spinning waiting for the successor and that the job of passing the lock is not done in the unlock path either.
Quoted 5 lines
Quoted 3 lines
Instead all of that is now done in the path of the lock acquirer logic.
Could you update the comment to say that please?
I _think_ I know what you mean.. So that is actually implied by the last
You do :-)
Quoted 17 lines
paragraph, but I suppose I can make it explicit; something like:
*
* Another way to look at it is:
*
*lock(tail,locked)* struct mcs_spinlock node; * mcs_spin_lock(tail, &node); * test-and-set locked; * mcs_spin_unlock(tail, &node);*
*unlock(tail,locked)
* clear locked
*
* Where we have compressed (tail,locked) into a single u32 word.
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Peter ZijlstraQuoted 29 lines · Peter Zijlstra
Quoted 28 lines · Konrad Rzeszutek Wilk
Quoted 15 lines
+ * The basic principle of a queue-based spinlock can best be understood
+ * by studying a classic queue-based spinlock implementation called the
+ * MCS lock. The paper below provides a good description for this kind
+ * of lock.
+ *
+ * http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf
+ *
+ * This queue spinlock implementation is based on the MCS lock, however to make
+ * it fit the 4 bytes we assumespinlock_tto be, and preserve its existing
+ * API, we must modify it some.
+ *
+ * In particular; where the traditional MCS lock consists of a tail pointer
+ * (8 bytes) and needs the next pointer (another 8 bytes) of its own node to
+ * unlock the next pending (next->locked), we compress both these: {tail,
+ *next->locked} into a single u32 value.Quoted 8 lines
+ *
+ * Since a spinlock disables recursion of its own context and there is a limit
+ * to the contexts that can nest; namely: task, softirq, hardirq, nmi, we can
+ * encode the tail as and index indicating this context and a cpu number.
+ *
+ * We can further change the first spinner to spin on a bit in the lock word
+ * instead of its node; whereby avoiding the need to carry a node from lock to
+ * unlock, and preserving API.You also made changes (compared to the MCS) in that the unlock path is not spinning waiting for the successor and that the job of passing the lock is not done in the unlock path either.
Quoted 5 lines
Quoted 3 lines
Instead all of that is now done in the path of the lock acquirer logic.
Could you update the comment to say that please?
I _think_ I know what you mean.. So that is actually implied by the last
You do :-)
Quoted 17 lines
paragraph, but I suppose I can make it explicit; something like:
*
* Another way to look at it is:
*
*lock(tail,locked)* struct mcs_spinlock node; * mcs_spin_lock(tail, &node); * test-and-set locked; * mcs_spin_unlock(tail, &node);*
*unlock(tail,locked)
* clear locked
*
* Where we have compressed (tail,locked) into a single u32 word.
Peter Zijlstra
maintainerRe: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Konrad Rzeszutek WilkQuoted 24 lines · Konrad Rzeszutek Wilk
Quoted 15 lines
+ * The basic principle of a queue-based spinlock can best be understood
+ * by studying a classic queue-based spinlock implementation called the
+ * MCS lock. The paper below provides a good description for this kind
+ * of lock.
+ *
+ * http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf
+ *
+ * This queue spinlock implementation is based on the MCS lock, however to make
+ * it fit the 4 bytes we assumespinlock_tto be, and preserve its existing
+ * API, we must modify it some.
+ *
+ * In particular; where the traditional MCS lock consists of a tail pointer
+ * (8 bytes) and needs the next pointer (another 8 bytes) of its own node to
+ * unlock the next pending (next->locked), we compress both these: {tail,
+ *next->locked} into a single u32 value.Quoted 8 lines
+ *
+ * Since a spinlock disables recursion of its own context and there is a limit
+ * to the contexts that can nest; namely: task, softirq, hardirq, nmi, we can
+ * encode the tail as and index indicating this context and a cpu number.
+ *
+ * We can further change the first spinner to spin on a bit in the lock word
+ * instead of its node; whereby avoiding the need to carry a node from lock to
+ * unlock, and preserving API.
Quoted 7 lines
You also made changes (compared to the MCS) in that the unlock path is not spinning waiting for the successor and that the job of passing the lock is not done in the unlock path either.
Instead all of that is now done in the path of the lock acquirer logic.
Could you update the comment to say that please?
I _think_ I know what you mean.. So that is actually implied by the last paragraph, but I suppose I can make it explicit; something like:
*
* Another way to look at it is:
*
* lock(tail,locked)
* struct mcs_spinlock node; * mcs_spin_lock(tail, &node); * test-and-set locked; * mcs_spin_unlock(tail, &node);
*
* unlock(tail,locked)
* clear locked
*
* Where we have compressed (tail,locked) into a single u32 word.
Re: [PATCH 01/11] qspinlock: A simple generic 4-byte queue spinlock
In reply to Peter ZijlstraQuoted 15 lines
+ * The basic principle of a queue-based spinlock can best be understood
+ * by studying a classic queue-based spinlock implementation called the
+ * MCS lock. The paper below provides a good description for this kind
+ * of lock.
+ *
+ * http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf
+ *
+ * This queue spinlock implementation is based on the MCS lock, however to make
+ * it fit the 4 bytes we assumespinlock_tto be, and preserve its existing
+ * API, we must modify it some.
+ *
+ * In particular; where the traditional MCS lock consists of a tail pointer
+ * (8 bytes) and needs the next pointer (another 8 bytes) of its own node to
+ * unlock the next pending (next->locked), we compress both these: {tail,
+ *next->locked} into a single u32 value.
Quoted 8 lines
+ *
+ * Since a spinlock disables recursion of its own context and there is a limit
+ * to the contexts that can nest; namely: task, softirq, hardirq, nmi, we can
+ * encode the tail as and index indicating this context and a cpu number.
+ *
+ * We can further change the first spinner to spin on a bit in the lock word
+ * instead of its node; whereby avoiding the need to carry a node from lock to
+ * unlock, and preserving API.
You also made changes (compared to the MCS) in that the unlock path is not spinning waiting for the successor and that the job of passing the lock is not done in the unlock path either.
Instead all of that is now done in the path of the lock acquirer logic.
Could you update the comment to say that please?
Thanks.
PATCH 1/11 qspinlock: A simple generic 4-byte queue spinlock
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch introduces a new generic queue spinlock implementation that can serve as an alternative to the default ticket spinlock. Compared with the ticket spinlock, this queue spinlock should be almost as fair as the ticket spinlock. It has about the same speed in single-thread and it can be much faster in high contention situations especially when the spinlock is embedded within the data structure to be protected.
Only in light to moderate contention where the average queue depth is around 1-3 will this queue spinlock be potentially a bit slower due to the higher slowpath overhead.
This queue spinlock is especially suit to NUMA machines with a large number of cores as the chance of spinlock contention is much higher in those machines. The cost of contention is also higher because of slower inter-node memory traffic.
Due to the fact that spinlocks are acquired with preemption disabled, the process will not be migrated to another CPU while it is trying to get a spinlock. Ignoring interrupt handling, a CPU can only be contending in one spinlock at any one time. Counting soft IRQ, hard IRQ and NMI, a CPU can only have a maximum of 4 concurrent lock waiting activities. By allocating a set of per-cpu queue nodes and used them to form a waiting queue, we can encode the queue node address into a much smaller 24-bit size (including CPU number and queue node index) leaving one byte for the lock.
Please note that the queue node is only needed when waiting for the lock. Once the lock is acquired, the queue node can be released to be used later.
--- include/asm-generic/qspinlock.h | 118 ++++++++++++++++++++ include/asm-generic/qspinlock_types.h | 61 ++++++++++ kernel/Kconfig.locks | 7 + kernel/locking/Makefile | 1 kernel/locking/mcs_spinlock.h | 1 kernel/locking/qspinlock.c | 197 ++++++++++++++++++++++++++++++++++ 6 files changed, 385 insertions(+) create mode 100644 include/asm-generic/qspinlock.h create mode 100644 include/asm-generic/qspinlock_types.h create mode 100644 kernel/locking/qspinlock.c
include/asm-generic/qspinlock.h new file +118 -0
@@ -0,0 +1,118 @@ +/* +* Queue spinlock +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* (C) Copyright 2013-2014 Hewlett-Packard Development Company, L.P. +* +* Authors: Waiman Long <[email protected]> +*/ +#ifndef __ASM_GENERIC_QSPINLOCK_H +#define __ASM_GENERIC_QSPINLOCK_H + +#include <asm-generic/qspinlock_types.h> + +/** +* queue_spin_is_locked - is the spinlock locked? +* @lock: Pointer to queue spinlock structure +* Return: 1 if it is locked, 0 otherwise +*/ +static __always_inline int queue_spin_is_locked(struct qspinlock *lock) +{ +return atomic_read (&lock->val); +} + +/** +* queue_spin_value_unlocked - is the spinlock structure unlocked? +* @lock: queue spinlock structure +* Return: 1 if it is unlocked, 0 otherwise +* +* N.B. Whenever there are tasks waiting for the lock, it is considered +* locked wrt the lockref code to avoid lock stealing by the lockref +* code and change things underneath the lock. This also allows some +* optimizations to be applied without conflict with lockref. +*/ +static __always_inline int queue_spin_value_unlocked(struct qspinlock lock) +{ +return !atomic_read (&lock.val); +} + +/** +* queue_spin_is_contended - check if the lock is contended +* @lock : Pointer to queue spinlock structure +* Return: 1 if lock contended, 0 otherwise +*/ +static __always_inline int queue_spin_is_contended(struct qspinlock *lock) +{ +return atomic_read (&lock->val) & ~_Q_LOCKED_MASK; +} +/** +* queue_spin_trylock - try to acquire the queue spinlock +* @lock : Pointer to queue spinlock structure +* Return: 1 if lock acquired, 0 if failed +*/ +static __always_inline int queue_spin_trylock(struct qspinlock *lock) +{ +if (!atomic_read (&lock->val) && + (atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL) ==0 )) +return 1 ; +return 0 ; +} + +extern void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val); + +/** +* queue_spin_lock - acquire a queue spinlock +* @lock: Pointer to queue spinlock structure +*/ +static __always_inline void queue_spin_lock(struct qspinlock *lock) +{ +u32 val; + + val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL); +if (likely (val ==0 )) +return ; +queue_spin_lock_slowpath (lock, val); +} + +#ifndef queue_spin_unlock +/** +* queue_spin_unlock - release a queue spinlock +* @lock : Pointer to queue spinlock structure +*/ +static __always_inline void queue_spin_unlock(struct qspinlock *lock) +{ +/* +* smp_mb__before_atomic() in order to guarantee release semantics +*/ +smp_mb__before_atomic_dec (); +atomic_sub (_Q_LOCKED_VAL, &lock->val); +} +#endif + +/* +* Initializier +*/ +#define __ARCH_SPIN_LOCK_UNLOCKED { ATOMIC_INIT(0) } + +/* +* Remapping spinlock architecture specific functions to the corresponding +* queue spinlock functions. +*/ +#define arch_spin_is_locked (l) queue_spin_is_locked(l) +#define arch_spin_is_contended (l) queue_spin_is_contended(l) +#define arch_spin_value_unlocked (l) queue_spin_value_unlocked(l) +#define arch_spin_lock (l) queue_spin_lock(l) +#define arch_spin_trylock (l) queue_spin_trylock(l) +#define arch_spin_unlock (l) queue_spin_unlock(l) +#define arch_spin_lock_flags (l, f) queue_spin_lock(l) + +#endif /* __ASM_GENERIC_QSPINLOCK_H */
include/asm-generic/qspinlock_types.h new file +61 -0
61 lines not on this page · raw message
kernel/Kconfig.locks +7 -0
@@ -224,6 +224,13 @@ config MUTEX_SPIN_ON_OWNER def_bool y depends on SMP && !DEBUG_MUTEXES && !PARISC +config ARCH_USE_QUEUE_SPINLOCK + bool + +config QUEUE_SPINLOCK + def_bool y if ARCH_USE_QUEUE_SPINLOCK + depends on SMP && !PARAVIRT_SPINLOCKS + config ARCH_USE_QUEUE_RWLOCK bool
kernel/locking/Makefile +1 -0
@@ -16,6 +16,7 @@ endif obj-$(CONFIG_SMP) += spinlock.o obj-$(CONFIG_SMP) += lglock.o obj-$(CONFIG_PROVE_LOCKING) += spinlock.o +obj-$(CONFIG_QUEUE_SPINLOCK) += qspinlock.o obj-$(CONFIG_RT_MUTEXES) += rtmutex.o obj-$(CONFIG_DEBUG_RT_MUTEXES) += rtmutex-debug.o obj-$(CONFIG_RT_MUTEX_TESTER) += rtmutex-tester.o
kernel/locking/mcs_spinlock.h +1 -0
@@ -17,6 +17,7 @@struct mcs_spinlock {struct mcs_spinlock *next;int locked;/* 1 if lock acquired */ +int count; };#ifndef arch_mcs_spin_lock_contended
kernel/locking/qspinlock.c new file +197 -0
197 lines not on this page · raw message
PATCH 2/11 qspinlock, x86: Enable x86-64 to use queue spinlock
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch makes the necessary changes at the x86 architecture specific layer to enable the use of queue spinlock for x86-64. As x86-32 machines are typically not multi-socket. The benefit of queue spinlock may not be apparent. So queue spinlock is not enabled.
Currently, there is some incompatibilities between the para-virtualized spinlock code (which hard-codes the use of ticket spinlock) and the queue spinlock. Therefore, the use of queue spinlock is disabled when the para-virtualized spinlock is enabled.
The arch/x86/include/asm/qspinlock.h header file includes some x86 specific optimization which will make the queue spinlock code perform better than the generic implementation.
--- arch/x86/Kconfig | 1 + arch/x86/include/asm/qspinlock.h | 25 +++++++++++++++++++++++++ arch/x86/include/asm/spinlock.h | 5 +++++ arch/x86/include/asm/spinlock_types.h | 4 ++++ 4 files changed, 35 insertions(+) create mode 100644 arch/x86/include/asm/qspinlock.h
arch/x86/Kconfig +1 -0
@@ -29,6 +29,7 @@ config X86 select ARCH_SUPPORTS_NUMA_BALANCING if X86_64 select ARCH_SUPPORTS_INT128 if X86_64 select ARCH_WANTS_PROT_NUMA_PROT_NONE + select ARCH_USE_QUEUE_SPINLOCK select HAVE_IDE select HAVE_OPROFILE select HAVE_PCSPKR_PLATFORM
arch/x86/include/asm/qspinlock.h new file +25 -0
@@ -0,0 +1,25 @@ +#ifndef _ASM_X86_QSPINLOCK_H +#define _ASM_X86_QSPINLOCK_H + +#include <asm-generic/qspinlock_types.h> + +#if !defined(CONFIG_X86_OOSTORE ) && !defined(CONFIG_X86_PPRO_FENCE ) + +#define queue_spin_unlock queue_spin_unlock +/** +* queue_spin_unlock - release a queue spinlock +* @lock : Pointer to queue spinlock structure +* +* An effective smp_store_release() on the least-significant byte. +*/ +static inline void queue_spin_unlock (struct qspinlock *lock) +{ +barrier (); +ACCESS_ONCE (*(u8 *)lock) =0 ; +} + +#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */ + +#include <asm-generic/qspinlock.h> + +#endif /* _ASM_X86_QSPINLOCK_H */
arch/x86/include/asm/spinlock.h +5 -0
@@ -42,6 +42,10 @@extern struct static_key paravirt_ticketlocks_enabled; static __always_inline bool static_key_false(struct static_key *key); +#ifdef CONFIG_QUEUE_SPINLOCK +#include <asm/qspinlock.h> +#else +#ifdef CONFIG_PARAVIRT_SPINLOCKS static inline void __ticket_enter_slowpath (arch_spinlock_t *lock)
@@ -180,6 +184,7 @@ static __always_inline void arch_spin_lo {arch_spin_lock (lock); } +#endif /* CONFIG_QUEUE_SPINLOCK */ static inline void arch_spin_unlock_wait (arch_spinlock_t *lock) {
arch/x86/include/asm/spinlock_types.h +4 -0
@@ -23,6 +23,9 @@ typedef u32 __ticketpair_t;#define TICKET_SHIFT (sizeof(__ticket_t) * 8) +#ifdef CONFIG_QUEUE_SPINLOCK +#include <asm-generic/qspinlock_types.h> +#else typedef struct arch_spinlock { union { __ticketpair_t head_tail;
@@ -33,6 +36,7 @@ typedef struct arch_spinlock { } arch_spinlock_t;#define __ARCH_SPIN_LOCK_UNLOCKED { { 0 } } +#endif /* CONFIG_QUEUE_SPINLOCK */ #ifdef CONFIG_QUEUE_RWLOCK #include <asm-generic/qrwlock_types.h>
PATCH 2/11 qspinlock, x86: Enable x86-64 to use queue spinlock
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch makes the necessary changes at the x86 architecture specific layer to enable the use of queue spinlock for x86-64. As x86-32 machines are typically not multi-socket. The benefit of queue spinlock may not be apparent. So queue spinlock is not enabled.
Currently, there is some incompatibilities between the para-virtualized spinlock code (which hard-codes the use of ticket spinlock) and the queue spinlock. Therefore, the use of queue spinlock is disabled when the para-virtualized spinlock is enabled.
The arch/x86/include/asm/qspinlock.h header file includes some x86 specific optimization which will make the queue spinlock code perform better than the generic implementation.
--- arch/x86/Kconfig | 1 + arch/x86/include/asm/qspinlock.h | 25 +++++++++++++++++++++++++ arch/x86/include/asm/spinlock.h | 5 +++++ arch/x86/include/asm/spinlock_types.h | 4 ++++ 4 files changed, 35 insertions(+) create mode 100644 arch/x86/include/asm/qspinlock.h
arch/x86/Kconfig +1 -0
@@ -29,6 +29,7 @@ config X86 select ARCH_SUPPORTS_NUMA_BALANCING if X86_64 select ARCH_SUPPORTS_INT128 if X86_64 select ARCH_WANTS_PROT_NUMA_PROT_NONE + select ARCH_USE_QUEUE_SPINLOCK select HAVE_IDE select HAVE_OPROFILE select HAVE_PCSPKR_PLATFORM
arch/x86/include/asm/qspinlock.h new file +25 -0
@@ -0,0 +1,25 @@ +#ifndef _ASM_X86_QSPINLOCK_H +#define _ASM_X86_QSPINLOCK_H + +#include <asm-generic/qspinlock_types.h> + +#if !defined(CONFIG_X86_OOSTORE ) && !defined(CONFIG_X86_PPRO_FENCE ) + +#define queue_spin_unlock queue_spin_unlock +/** +* queue_spin_unlock - release a queue spinlock +* @lock : Pointer to queue spinlock structure +* +* An effective smp_store_release() on the least-significant byte. +*/ +static inline void queue_spin_unlock (struct qspinlock *lock) +{ +barrier (); +ACCESS_ONCE (*(u8 *)lock) =0 ; +} + +#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */ + +#include <asm-generic/qspinlock.h> + +#endif /* _ASM_X86_QSPINLOCK_H */
arch/x86/include/asm/spinlock.h +5 -0
@@ -42,6 +42,10 @@extern struct static_key paravirt_ticketlocks_enabled; static __always_inline bool static_key_false(struct static_key *key); +#ifdef CONFIG_QUEUE_SPINLOCK +#include <asm/qspinlock.h> +#else +#ifdef CONFIG_PARAVIRT_SPINLOCKS static inline void __ticket_enter_slowpath (arch_spinlock_t *lock)
@@ -180,6 +184,7 @@ static __always_inline void arch_spin_lo {arch_spin_lock (lock); } +#endif /* CONFIG_QUEUE_SPINLOCK */ static inline void arch_spin_unlock_wait (arch_spinlock_t *lock) {
arch/x86/include/asm/spinlock_types.h +4 -0
@@ -23,6 +23,9 @@ typedef u32 __ticketpair_t;#define TICKET_SHIFT (sizeof(__ticket_t) * 8) +#ifdef CONFIG_QUEUE_SPINLOCK +#include <asm-generic/qspinlock_types.h> +#else typedef struct arch_spinlock { union { __ticketpair_t head_tail;
@@ -33,6 +36,7 @@ typedef struct arch_spinlock { } arch_spinlock_t;#define __ARCH_SPIN_LOCK_UNLOCKED { { 0 } } +#endif /* CONFIG_QUEUE_SPINLOCK */ #ifdef CONFIG_QUEUE_RWLOCK #include <asm-generic/qrwlock_types.h>
PATCH 2/11 qspinlock, x86: Enable x86-64 to use queue spinlock
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch makes the necessary changes at the x86 architecture specific layer to enable the use of queue spinlock for x86-64. As x86-32 machines are typically not multi-socket. The benefit of queue spinlock may not be apparent. So queue spinlock is not enabled.
Currently, there is some incompatibilities between the para-virtualized spinlock code (which hard-codes the use of ticket spinlock) and the queue spinlock. Therefore, the use of queue spinlock is disabled when the para-virtualized spinlock is enabled.
The arch/x86/include/asm/qspinlock.h header file includes some x86 specific optimization which will make the queue spinlock code perform better than the generic implementation.
--- arch/x86/Kconfig | 1 + arch/x86/include/asm/qspinlock.h | 25 +++++++++++++++++++++++++ arch/x86/include/asm/spinlock.h | 5 +++++ arch/x86/include/asm/spinlock_types.h | 4 ++++ 4 files changed, 35 insertions(+) create mode 100644 arch/x86/include/asm/qspinlock.h
arch/x86/Kconfig +1 -0
@@ -29,6 +29,7 @@ config X86 select ARCH_SUPPORTS_NUMA_BALANCING if X86_64 select ARCH_SUPPORTS_INT128 if X86_64 select ARCH_WANTS_PROT_NUMA_PROT_NONE + select ARCH_USE_QUEUE_SPINLOCK select HAVE_IDE select HAVE_OPROFILE select HAVE_PCSPKR_PLATFORM
arch/x86/include/asm/qspinlock.h new file +25 -0
@@ -0,0 +1,25 @@ +#ifndef _ASM_X86_QSPINLOCK_H +#define _ASM_X86_QSPINLOCK_H + +#include <asm-generic/qspinlock_types.h> + +#if !defined(CONFIG_X86_OOSTORE ) && !defined(CONFIG_X86_PPRO_FENCE ) + +#define queue_spin_unlock queue_spin_unlock +/** +* queue_spin_unlock - release a queue spinlock +* @lock : Pointer to queue spinlock structure +* +* An effective smp_store_release() on the least-significant byte. +*/ +static inline void queue_spin_unlock (struct qspinlock *lock) +{ +barrier (); +ACCESS_ONCE (*(u8 *)lock) =0 ; +} + +#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */ + +#include <asm-generic/qspinlock.h> + +#endif /* _ASM_X86_QSPINLOCK_H */
arch/x86/include/asm/spinlock.h +5 -0
@@ -42,6 +42,10 @@extern struct static_key paravirt_ticketlocks_enabled; static __always_inline bool static_key_false(struct static_key *key); +#ifdef CONFIG_QUEUE_SPINLOCK +#include <asm/qspinlock.h> +#else +#ifdef CONFIG_PARAVIRT_SPINLOCKS static inline void __ticket_enter_slowpath (arch_spinlock_t *lock)
@@ -180,6 +184,7 @@ static __always_inline void arch_spin_lo {arch_spin_lock (lock); } +#endif /* CONFIG_QUEUE_SPINLOCK */ static inline void arch_spin_unlock_wait (arch_spinlock_t *lock) {
arch/x86/include/asm/spinlock_types.h +4 -0
@@ -23,6 +23,9 @@ typedef u32 __ticketpair_t;#define TICKET_SHIFT (sizeof(__ticket_t) * 8) +#ifdef CONFIG_QUEUE_SPINLOCK +#include <asm-generic/qspinlock_types.h> +#else typedef struct arch_spinlock { union { __ticketpair_t head_tail;
@@ -33,6 +36,7 @@ typedef struct arch_spinlock { } arch_spinlock_t;#define __ARCH_SPIN_LOCK_UNLOCKED { { 0 } } +#endif /* CONFIG_QUEUE_SPINLOCK */ #ifdef CONFIG_QUEUE_RWLOCK #include <asm-generic/qrwlock_types.h>
PATCH 3/11 qspinlock: Add pending bit
In reply to Peter ZijlstraBecause the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
--- include/asm-generic/qspinlock_types.h | 12 ++- kernel/locking/qspinlock.c | 109 +++++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 24 deletions(-)
include/asm-generic/qspinlock_types.h +9 -3
@@ -39,8 +39,9 @@ typedef struct qspinlock {* Bitfields in the atomic value: * * 0- 7: locked byte -* 8- 9: tail index -* 10-31: tail cpu (+1) +* 8: pending +* 9-10: tail index +* 11-31: tail cpu (+1) */ #define _Q_SET_MASK (type) (((1U << _Q_ ## type ## _BITS) - 1)\ << _Q_ ## type ## _OFFSET)
@@ -48,7 +49,11 @@ typedef struct qspinlock {#define _Q_LOCKED_BITS 8#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED) -#define _Q_TAIL_IDX_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#define _Q_PENDING_BITS 1 +#define _Q_PENDING_MASK _Q_SET_MASK(PENDING) + +#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)#define _Q_TAIL_IDX_BITS 2#define _Q_TAIL_IDX_MASK _Q_SET_MASK(TAIL_IDX)
@@ -57,5 +62,6 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU)#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET) +#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)#endif /* __ASM_GENERIC_QSPINLOCK_TYPES_H */
kernel/locking/qspinlock.c +88 -21
158 lines not on this page · raw message
Re: [PATCH 03/11] qspinlock: Add pending bit
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]
Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +11 -1
@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new; val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ; + val = old; + }queue_spin_lock_slowpath (lock, val); }
and then the slowpath preserves most of the old logic path (with the pending bit stuff)?
Quoted 208 lines
Signed-off-by Peter Zijlstra <[email protected]>--- include/asm-generic/qspinlock_types.h | 12 ++- kernel/locking/qspinlock.c | 109 +++++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 24 deletions(-)include/asm-generic/qspinlock_types.h +9
-3@@ -39,8 +39,9 @@ typedef struct qspinlock {* Bitfields in the atomic value: * * 0- 7: locked byte -* 8- 9: tail index -* 10-31: tail cpu (+1) +* 8: pending +* 9-10: tail index +* 11-31: tail cpu (+1) */ #define _Q_SET_MASK (type) (((1U << _Q_ ## type ## _BITS) - 1)\ << _Q_ ## type ## _OFFSET)@@ -48,7 +49,11 @@ typedef struct qspinlock {#define _Q_LOCKED_BITS 8#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED) -#define _Q_TAIL_IDX_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#define _Q_PENDING_BITS 1 +#define _Q_PENDING_MASK _Q_SET_MASK(PENDING) + +#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)#define _Q_TAIL_IDX_BITS 2#define _Q_TAIL_IDX_MASK _Q_SET_MASK(TAIL_IDX)@@ -57,5 +62,6 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU)#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET) +#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)#endif /* __ASM_GENERIC_QSPINLOCK_TYPES_H */ kernel/locking/qspinlock.c +88
-21@@ -83,24 +83,28 @@ static inline struct mcs_spinlock *decodreturn per_cpu_ptr (&mcs_nodes[idx], cpu); } +#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK) +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word * -* (queue tail, lock bit) -* -* fast : slow : unlock -* : : -* uncontended (0,0) --:--> (0,1) --------------------------------:--> (*,0) -* : | ^--------. / : -* : v \ | : -* uncontended : (n,x) --+--> (n,0) | : -* queue : | ^--' | : -* : v | : -* contended : (*,x) --+--> (*,0) -----> (*,1) ---' : -* queue : ^--' : +* (queue tail, pending bit, lock bit) * +* fast : slow : unlock +* : : +* uncontended (0,0,0) -:--> (0,0,1) ------------------------------:--> (*,*,0) +* : | ^--------.------. / : +* : v \ \ | : +* pending : (0,1,1) +--> (0,1,0) \ | : +* : | ^--' | | : +* : v | | : +* uncontended : (n,x,y) +--> (n,0,0) --' | : +* queue : | ^--' | : +* : v | : +* contended : (*,x,y) +--> (*,0,0) ---> (*,0,1) -' : +* queue : ^--' : */ void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val) {@@ -110,6 +114,65 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +/* +* trylock || pending +* +* 0,0,0 -> 0,0,1 ; trylock +* 0,0,1 -> 0,1,1 ; pending +*/ +for (;;) { +/* +* If we observe any contention; queue. +*/ +if (val & ~_Q_LOCKED_MASK) + goto queue; + + new = _Q_LOCKED_VAL; +if (val == new) + new |= _Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock +*/ +if (new == _Q_LOCKED_VAL) +return ; + +/* +* we're pending, wait for the owner to go away. +* +* *,1,1 -> *,1,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); + +/* +* take ownership and clear the pending bit. +* +* *,1,0 -> *,0,1 +*/ +for (;;) { + new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +return ; + +/* +* End of pending bit optimistic spinning and beginning of MCS +* queuing. +*/ +queue: node =this_cpu_ptr (&mcs_nodes[0 ]); idx = node->count++; tail =encode_tail (smp_processor_id (), idx);@@ -119,15 +182,18 @@ void queue_spin_lock_slowpath(struct qsp node->next =NULL ;/* +* we already touched the queueing cacheline; don't bother with pending +* stuff. +* * trylock || xchg(lock, node) * -* 0,0 -> 0,1 ; trylock -* p,x -> n,x ; prev = xchg(lock, node) +* 0,0,0 -> 0,0,1 ; trylock +* p,y,x -> n,y,x ; prev = xchg(lock, node) */ for (;;) { new = _Q_LOCKED_VAL;if (val) - new = tail | (val & _Q_LOCKED_MASK); + new = tail | (val & _Q_LOCKED_PENDING_MASK); old =atomic_cmpxchg (&lock->val, val, new);if (old == val)@@ -145,7 +211,7 @@ void queue_spin_lock_slowpath(struct qsp/* * if there was a previous node; link it and wait. */ -if (old & ~_Q_LOCKED_MASK) { +if (old & ~_Q_LOCKED_PENDING_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;@@ -153,18 +219,19 @@ void queue_spin_lock_slowpath(struct qsp }/* -* we're at the head of the waitqueue, wait for the owner to go away. +* we're at the head of the waitqueue, wait for the owner & pending to +* go away. * -* *,x -> *,0 +* *,x,y -> *,0,0 */ -while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_PENDING_MASK)cpu_relax ();/* * claim the lock: * -* n,0 -> 0,1 : lock, uncontended -* *,0 -> *,1 : lock, contended +* n,0,0 -> 0,0,1 : lock, uncontended +* *,0,0 -> *,0,1 : lock, contended */ for (;;) { new = _Q_LOCKED_VAL;
Re: [PATCH 03/11] qspinlock: Add pending bit
Quoted 28 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +1
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new; val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 90
Quoted 10 lines
include/asm-generic/qspinlock.h +8
-0if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ;
No, it can leave like that. The unlock path will not clear the pending bit. We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Moreover, an cmpxchg followed immediately followed by another cmpxchg will just increase the level of memory contention when a lock is fairly contended. The chance of second cmpxchg() succeeding will be pretty low.
-Longman
Re: [PATCH 03/11] qspinlock: Add pending bit
Quoted 28 lines · Waiman Long
Quoted 27 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +1
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new;
Quoted 13 lines
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 90
Quoted 11 lines
include/asm-generic/qspinlock.h +8
-0val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ;No, it can leave like that. The unlock path will not clear the pending bit.
Err, you are right. It needs to go back in the slowpath.
Quoted 2 lines
We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Sure, but then it shouldn't be called slowpath anymore as it is not slow. It is a combination of fast path (the potential chance of grabbing the lock and setting the pending lock) and the real slow path (the queuing). Perhaps it should be called 'queue_spinlock_complex' ?
Quoted 4 lines
Moreover, an cmpxchg followed immediately followed by another cmpxchg will just increase the level of memory contention when a lock is fairly contended. The chance of second
cmpxchg()succeeding will be pretty low.
Then why even do the pending bit - which is what the slowpath does for the first time. And if it grabs it (And sets the pending bit) it immediately exits. Why not perculate that piece of code in-to this header.
And the leave all that slow code (queing, mcs_lock access, etc) in the slowpath.
Quoted 4 lines
-Longman
Re: [PATCH 03/11] qspinlock: Add pending bit
Quoted 43 lines · Konrad Rzeszutek Wilk
Quoted 28 lines · Waiman Long
Quoted 27 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +1
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new;Quoted 13 lines
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 90
Quoted 11 lines
include/asm-generic/qspinlock.h +8
-0val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ;No, it can leave like that. The unlock path will not clear the pending bit.
Quoted 1 line
Err, you are right. It needs to go back in the slowpath.
What I should have wrote is:
if (old == 0) /* YEEY */
return;
As that would the same thing as this patch does on the pending bit - that is if we can on the second compare and exchange set the pending bit (and the lock) and the lock has been released - we are good.
And it is a quick path.
Quoted 9 lines
Quoted 3 lines
We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Sure, but then it shouldn't be called slowpath anymore as it is not slow. It is a combination of fast path (the potential chance of grabbing the lock and setting the pending lock) and the real slow path (the queuing). Perhaps it should be called '
queue_spinlock_complex' ?
I forgot to mention - that was the crux of my comments - just change the slowpath to complex name at that point to better reflect what it does.
Quoted 15 lines
Quoted 4 lines
Moreover, an cmpxchg followed immediately followed by another cmpxchg will just increase the level of memory contention when a lock is fairly contended. The chance of second
cmpxchg()succeeding will be pretty low.Then why even do the pending bit - which is what the slowpath does for the first time. And if it grabs it (And sets the pending bit) it immediately exits. Why not perculate that piece of code in-to this header.
And the leave all that slow code (queing,
mcs_lockaccess, etc) in the slowpath.Quoted 4 lines
-Longman
Re: [PATCH 03/11] qspinlock: Add pending bit
Quoted 42 lines · Konrad Rzeszutek Wilk
Quoted 41 lines · Konrad Rzeszutek Wilk
Quoted 40 lines · Waiman Long
Quoted 28 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +1
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new; val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 90
Quoted 10 lines
include/asm-generic/qspinlock.h +8
-0if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ;No, it can leave like that. The unlock path will not clear the pending bit.
Quoted 5 lines
Quoted 1 line
Err, you are right. It needs to go back in the slowpath.
What I should have wrote is:
if (old == 0) /* YEEY */
return;
Unfortunately, that still doesn't work. If old is 0, it just meant the cmpxchg failed. It still haven't got the lock.
Quoted 3 lines
As that would the same thing as this patch does on the pending bit - that is if we can on the second compare and exchange set the pending bit (and the lock) and the lock has been released - we are good.
That is not true. When the lock is freed, the pending bit holder will still have to clear the pending bit and set the lock bit as is done in the slowpath. We cannot skip the step here. The problem of moving the pending code here is that it includes a wait loop which we don't want to put in the fastpath.
Quoted 5 lines
And it is a quick path.
Quoted 2 lines
Quoted 2 lines
We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Quoted 8 lines
Quoted 5 lines
Sure, but then it shouldn't be called slowpath anymore as it is not slow. It is a combination of fast path (the potential chance of grabbing the lock and setting the pending lock) and the real slow path (the queuing). Perhaps it should be called '
queue_spinlock_complex' ?I forgot to mention - that was the crux of my comments - just change the slowpath to complex name at that point to better reflect what it does.
Actually in my v11 patch, I subdivided the slowpath into a slowpath for the pending code and slowerpath for actual queuing. Perhaps, we could use quickpath and slowpath instead. Anyway, it is a minor detail that we can discuss after the core code get merged.
-Longman
Re: [PATCH 03/11] qspinlock: Add pending bit
In reply to Konrad Rzeszutek WilkQuoted 42 lines · Konrad Rzeszutek Wilk
Quoted 41 lines · Konrad Rzeszutek Wilk
Quoted 40 lines · Waiman Long
Quoted 28 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +1
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new; val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 90
Quoted 10 lines
include/asm-generic/qspinlock.h +8
-0if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ;No, it can leave like that. The unlock path will not clear the pending bit.
Quoted 5 lines
Quoted 1 line
Err, you are right. It needs to go back in the slowpath.
What I should have wrote is:
if (old == 0) /* YEEY */
return;
Unfortunately, that still doesn't work. If old is 0, it just meant the cmpxchg failed. It still haven't got the lock.
Quoted 3 lines
As that would the same thing as this patch does on the pending bit - that is if we can on the second compare and exchange set the pending bit (and the lock) and the lock has been released - we are good.
That is not true. When the lock is freed, the pending bit holder will still have to clear the pending bit and set the lock bit as is done in the slowpath. We cannot skip the step here. The problem of moving the pending code here is that it includes a wait loop which we don't want to put in the fastpath.
Quoted 5 lines
And it is a quick path.
Quoted 2 lines
Quoted 2 lines
We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Quoted 8 lines
Quoted 5 lines
Sure, but then it shouldn't be called slowpath anymore as it is not slow. It is a combination of fast path (the potential chance of grabbing the lock and setting the pending lock) and the real slow path (the queuing). Perhaps it should be called '
queue_spinlock_complex' ?I forgot to mention - that was the crux of my comments - just change the slowpath to complex name at that point to better reflect what it does.
Actually in my v11 patch, I subdivided the slowpath into a slowpath for the pending code and slowerpath for actual queuing. Perhaps, we could use quickpath and slowpath instead. Anyway, it is a minor detail that we can discuss after the core code get merged.
-Longman
Re: [PATCH 03/11] qspinlock: Add pending bit
In reply to Konrad Rzeszutek WilkQuoted 43 lines · Konrad Rzeszutek Wilk
Quoted 28 lines · Waiman Long
Quoted 27 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +1
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new;Quoted 13 lines
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 90
Quoted 11 lines
include/asm-generic/qspinlock.h +8
-0val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ;No, it can leave like that. The unlock path will not clear the pending bit.
Quoted 1 line
Err, you are right. It needs to go back in the slowpath.
What I should have wrote is:
if (old == 0) /* YEEY */
return;
As that would the same thing as this patch does on the pending bit - that is if we can on the second compare and exchange set the pending bit (and the lock) and the lock has been released - we are good.
And it is a quick path.
Quoted 9 lines
Quoted 3 lines
We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Sure, but then it shouldn't be called slowpath anymore as it is not slow. It is a combination of fast path (the potential chance of grabbing the lock and setting the pending lock) and the real slow path (the queuing). Perhaps it should be called '
queue_spinlock_complex' ?
I forgot to mention - that was the crux of my comments - just change the slowpath to complex name at that point to better reflect what it does.
Quoted 15 lines
Quoted 4 lines
Moreover, an cmpxchg followed immediately followed by another cmpxchg will just increase the level of memory contention when a lock is fairly contended. The chance of second
cmpxchg()succeeding will be pretty low.Then why even do the pending bit - which is what the slowpath does for the first time. And if it grabs it (And sets the pending bit) it immediately exits. Why not perculate that piece of code in-to this header.
And the leave all that slow code (queing,
mcs_lockaccess, etc) in the slowpath.Quoted 4 lines
-Longman
Peter Zijlstra
maintainerRe: [PATCH 03/11] qspinlock: Add pending bit
In reply to Konrad Rzeszutek WilkQuoted 5 lines · Konrad Rzeszutek Wilk
Quoted 2 lines
We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Sure, but then it shouldn't be called slowpath anymore as it is not slow.
Its common terminology to call the inline part the fast path and the out-of-line call on failure the slow path.
Peter Zijlstra
maintainerRe: [PATCH 03/11] qspinlock: Add pending bit
In reply to Konrad Rzeszutek WilkQuoted 5 lines · Konrad Rzeszutek Wilk
Quoted 2 lines
We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Sure, but then it shouldn't be called slowpath anymore as it is not slow.
Its common terminology to call the inline part the fast path and the out-of-line call on failure the slow path.
Re: [PATCH 03/11] qspinlock: Add pending bit
In reply to Waiman LongQuoted 28 lines · Waiman Long
Quoted 27 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +1
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new;
Quoted 13 lines
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 90
Quoted 11 lines
include/asm-generic/qspinlock.h +8
-0val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ;No, it can leave like that. The unlock path will not clear the pending bit.
Err, you are right. It needs to go back in the slowpath.
Quoted 2 lines
We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Sure, but then it shouldn't be called slowpath anymore as it is not slow. It is a combination of fast path (the potential chance of grabbing the lock and setting the pending lock) and the real slow path (the queuing). Perhaps it should be called 'queue_spinlock_complex' ?
Quoted 4 lines
Moreover, an cmpxchg followed immediately followed by another cmpxchg will just increase the level of memory contention when a lock is fairly contended. The chance of second
cmpxchg()succeeding will be pretty low.
Then why even do the pending bit - which is what the slowpath does for the first time. And if it grabs it (And sets the pending bit) it immediately exits. Why not perculate that piece of code in-to this header.
And the leave all that slow code (queing, mcs_lock access, etc) in the slowpath.
Quoted 4 lines
-Longman
Re: [PATCH 03/11] qspinlock: Add pending bit
In reply to Konrad Rzeszutek WilkQuoted 28 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +1
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new; val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 90
Quoted 10 lines
include/asm-generic/qspinlock.h +8
-0if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ;
No, it can leave like that. The unlock path will not clear the pending bit. We are trying to make the fastpath as simple as possible as it may be inlined. The complexity of the queue spinlock is in the slowpath.
Moreover, an cmpxchg followed immediately followed by another cmpxchg will just increase the level of memory contention when a lock is fairly contended. The chance of second cmpxchg() succeeding will be pretty low.
-Longman
Paolo Bonzini
maintainerRe: [PATCH 03/11] qspinlock: Add pending bit
In reply to Konrad Rzeszutek WilkIl 17/06/2014 22:36, Konrad Rzeszutek Wilk ha scritto:
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 92
Quoted 9 lines
include/asm-generic/qspinlock.h +9
-0+/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ; + val = old; + }
Note that Peter's code is in a for(;;) loop:
+ for (;;) {
+ /*
+ * If we observe any contention; queue.
+ */
+ if (val & ~_Q_LOCKED_MASK)
+ goto queue;
+
+ new = _Q_LOCKED_VAL;
+ if (val == new)
+ new |= _Q_PENDING_VAL;
+
+ old = atomic_cmpxchg(&lock->val, val, new);
+ if (old == val)
+ break;
+
+ val = old;
+ }
+
+ /*
+ * we won the trylock
+ */
+ if (new == _Q_LOCKED_VAL)
+ return;
So what you'd have is basically:
/*
* One more attempt if no one is already in queue. Perhaps
* they have unlocked the spinlock already.
*/
if (val == _Q_LOCKED_VAL && atomic_read(&lock->val) == 0) {
old = atomic_cmpxchg(&lock->val, 0, _Q_LOCKED_VAL);
if (old == 0) /* YEEY! */
return;
val = old;
}But I agree with Waiman that this is unlikely to trigger often enough. It does have to be handled in the slowpath for correctness, but the most likely path is (0,0,1)->(0,1,1).
Paolo
Re: [PATCH 03/11] qspinlock: Add pending bit
Quoted 51 lines · Paolo Bonzini
Il 17/06/2014 22:36, Konrad Rzeszutek Wilk ha scritto:
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 92
Quoted 9 lines
include/asm-generic/qspinlock.h +9
-0+/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ; + val = old; + }Note that Peter's code is in a for(;;) loop:
(quoted diff) +23
-0+ for (;;) { + /* + * If we observe any contention; queue. + */ + if (val & ~_Q_LOCKED_MASK) + goto queue; + + new = _Q_LOCKED_VAL; + if (val == new) + new |= _Q_PENDING_VAL; + + old = atomic_cmpxchg(&lock->val, val, new); + if (old == val) + break; + + val = old; + } + + /* + * we won the trylock + */ + if (new == _Q_LOCKED_VAL) + return;So what you'd have is basically:
/* * One more attempt if no one is already in queue. Perhaps * they have unlocked the spinlock already. */ if (val == _Q_LOCKED_VAL && atomic_read(&lock->val) == 0) { old = atomic_cmpxchg(&lock->val, 0, _Q_LOCKED_VAL); if (old == 0) /* YEEY! */ return; val = old; }
Quoted 3 lines
But I agree with Waiman that this is unlikely to trigger often enough. It does have to be handled in the slowpath for correctness, but the most likely path is (0,0,1)->(0,1,1).
<nods>
Quoted 2 lines
Paolo
Re: [PATCH 03/11] qspinlock: Add pending bit
In reply to Paolo BonziniQuoted 51 lines · Paolo Bonzini
Il 17/06/2014 22:36, Konrad Rzeszutek Wilk ha scritto:
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 92
Quoted 9 lines
include/asm-generic/qspinlock.h +9
-0+/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ; + val = old; + }Note that Peter's code is in a for(;;) loop:
(quoted diff) +23
-0+ for (;;) { + /* + * If we observe any contention; queue. + */ + if (val & ~_Q_LOCKED_MASK) + goto queue; + + new = _Q_LOCKED_VAL; + if (val == new) + new |= _Q_PENDING_VAL; + + old = atomic_cmpxchg(&lock->val, val, new); + if (old == val) + break; + + val = old; + } + + /* + * we won the trylock + */ + if (new == _Q_LOCKED_VAL) + return;So what you'd have is basically:
/* * One more attempt if no one is already in queue. Perhaps * they have unlocked the spinlock already. */ if (val == _Q_LOCKED_VAL && atomic_read(&lock->val) == 0) { old = atomic_cmpxchg(&lock->val, 0, _Q_LOCKED_VAL); if (old == 0) /* YEEY! */ return; val = old; }
Quoted 3 lines
But I agree with Waiman that this is unlikely to trigger often enough. It does have to be handled in the slowpath for correctness, but the most likely path is (0,0,1)->(0,1,1).
<nods>
Quoted 2 lines
Paolo
Paolo Bonzini
maintainerRe: [PATCH 03/11] qspinlock: Add pending bit
In reply to Konrad Rzeszutek WilkIl 17/06/2014 22:36, Konrad Rzeszutek Wilk ha scritto:
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 92
Quoted 9 lines
include/asm-generic/qspinlock.h +9
-0+/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ; + val = old; + }
Note that Peter's code is in a for(;;) loop:
+ for (;;) {
+ /*
+ * If we observe any contention; queue.
+ */
+ if (val & ~_Q_LOCKED_MASK)
+ goto queue;
+
+ new = _Q_LOCKED_VAL;
+ if (val == new)
+ new |= _Q_PENDING_VAL;
+
+ old = atomic_cmpxchg(&lock->val, val, new);
+ if (old == val)
+ break;
+
+ val = old;
+ }
+
+ /*
+ * we won the trylock
+ */
+ if (new == _Q_LOCKED_VAL)
+ return;
So what you'd have is basically:
/*
* One more attempt if no one is already in queue. Perhaps
* they have unlocked the spinlock already.
*/
if (val == _Q_LOCKED_VAL && atomic_read(&lock->val) == 0) {
old = atomic_cmpxchg(&lock->val, 0, _Q_LOCKED_VAL);
if (old == 0) /* YEEY! */
return;
val = old;
}But I agree with Waiman that this is unlikely to trigger often enough. It does have to be handled in the slowpath for correctness, but the most likely path is (0,0,1)->(0,1,1).
Paolo
Peter Zijlstra
maintainerRe: [PATCH 03/11] qspinlock: Add pending bit
In reply to Konrad Rzeszutek WilkQuoted 9 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]
Those should be the very same cacheline :), but yes, I can add something like that.
Quoted 23 lines
Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +3
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new; val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 94
Quoted 10 lines
include/asm-generic/qspinlock.h +8
-0+if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ; + val = old; + }queue_spin_lock_slowpath (lock, val); }
I think that's too big for an inline function.
Peter Zijlstra
maintainerRe: [PATCH 03/11] qspinlock: Add pending bit
In reply to Konrad Rzeszutek WilkQuoted 9 lines · Konrad Rzeszutek Wilk
Quoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]
Those should be the very same cacheline :), but yes, I can add something like that.
Quoted 23 lines
Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +3
-1@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new; val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */
include/asm-generic/qspinlock.h extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val); · line 94
Quoted 10 lines
include/asm-generic/qspinlock.h +8
-0+if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ; + val = old; + }queue_spin_lock_slowpath (lock, val); }
I think that's too big for an inline function.
Re: [PATCH 03/11] qspinlock: Add pending bit
In reply to Peter ZijlstraQuoted 3 lines · Peter Zijlstra
Because the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
Could you add this in the description please:
And by second cacheline we mean the local 'node'. That is the:
mcs_nodes[0] and mcs_nodes[idx]
Perhaps it might be better then to split this in the header file as this is trying to not be a slowpath code - but rather - a pre-slow-path-lets-try-if-we can do another cmpxchg in case the unlocker has just unlocked itself.
So something like:
include/asm-generic/qspinlock.h +11 -1
@@ -75,11 +75,21 @@ extern void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val);*/ static __always_inline void queue_spin_lock(struct qspinlock *lock) { -u32 val; +u32 val, new; val =atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL);if (likely (val ==0 ))return ; + +/* One more attempt - but if we fail mark it as pending. */ +if (val == _Q_LOCKED_VAL) { + new =Q_LOCKED_VAL |_Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == _Q_LOCKED_VAL)/* YEEY! */ +return ; + val = old; + }queue_spin_lock_slowpath (lock, val); }
and then the slowpath preserves most of the old logic path (with the pending bit stuff)?
Quoted 208 lines
Signed-off-by Peter Zijlstra <[email protected]>--- include/asm-generic/qspinlock_types.h | 12 ++- kernel/locking/qspinlock.c | 109 +++++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 24 deletions(-)include/asm-generic/qspinlock_types.h +9
-3@@ -39,8 +39,9 @@ typedef struct qspinlock {* Bitfields in the atomic value: * * 0- 7: locked byte -* 8- 9: tail index -* 10-31: tail cpu (+1) +* 8: pending +* 9-10: tail index +* 11-31: tail cpu (+1) */ #define _Q_SET_MASK (type) (((1U << _Q_ ## type ## _BITS) - 1)\ << _Q_ ## type ## _OFFSET)@@ -48,7 +49,11 @@ typedef struct qspinlock {#define _Q_LOCKED_BITS 8#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED) -#define _Q_TAIL_IDX_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#define _Q_PENDING_BITS 1 +#define _Q_PENDING_MASK _Q_SET_MASK(PENDING) + +#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)#define _Q_TAIL_IDX_BITS 2#define _Q_TAIL_IDX_MASK _Q_SET_MASK(TAIL_IDX)@@ -57,5 +62,6 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU)#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET) +#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)#endif /* __ASM_GENERIC_QSPINLOCK_TYPES_H */ kernel/locking/qspinlock.c +88
-21@@ -83,24 +83,28 @@ static inline struct mcs_spinlock *decodreturn per_cpu_ptr (&mcs_nodes[idx], cpu); } +#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK) +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word * -* (queue tail, lock bit) -* -* fast : slow : unlock -* : : -* uncontended (0,0) --:--> (0,1) --------------------------------:--> (*,0) -* : | ^--------. / : -* : v \ | : -* uncontended : (n,x) --+--> (n,0) | : -* queue : | ^--' | : -* : v | : -* contended : (*,x) --+--> (*,0) -----> (*,1) ---' : -* queue : ^--' : +* (queue tail, pending bit, lock bit) * +* fast : slow : unlock +* : : +* uncontended (0,0,0) -:--> (0,0,1) ------------------------------:--> (*,*,0) +* : | ^--------.------. / : +* : v \ \ | : +* pending : (0,1,1) +--> (0,1,0) \ | : +* : | ^--' | | : +* : v | | : +* uncontended : (n,x,y) +--> (n,0,0) --' | : +* queue : | ^--' | : +* : v | : +* contended : (*,x,y) +--> (*,0,0) ---> (*,0,1) -' : +* queue : ^--' : */ void queue_spin_lock_slowpath (struct qspinlock *lock,u32 val) {@@ -110,6 +114,65 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +/* +* trylock || pending +* +* 0,0,0 -> 0,0,1 ; trylock +* 0,0,1 -> 0,1,1 ; pending +*/ +for (;;) { +/* +* If we observe any contention; queue. +*/ +if (val & ~_Q_LOCKED_MASK) + goto queue; + + new = _Q_LOCKED_VAL; +if (val == new) + new |= _Q_PENDING_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } + +/* +* we won the trylock +*/ +if (new == _Q_LOCKED_VAL) +return ; + +/* +* we're pending, wait for the owner to go away. +* +* *,1,1 -> *,1,0 +*/ +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +cpu_relax (); + +/* +* take ownership and clear the pending bit. +* +* *,1,0 -> *,0,1 +*/ +for (;;) { + new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +return ; + +/* +* End of pending bit optimistic spinning and beginning of MCS +* queuing. +*/ +queue: node =this_cpu_ptr (&mcs_nodes[0 ]); idx = node->count++; tail =encode_tail (smp_processor_id (), idx);@@ -119,15 +182,18 @@ void queue_spin_lock_slowpath(struct qsp node->next =NULL ;/* +* we already touched the queueing cacheline; don't bother with pending +* stuff. +* * trylock || xchg(lock, node) * -* 0,0 -> 0,1 ; trylock -* p,x -> n,x ; prev = xchg(lock, node) +* 0,0,0 -> 0,0,1 ; trylock +* p,y,x -> n,y,x ; prev = xchg(lock, node) */ for (;;) { new = _Q_LOCKED_VAL;if (val) - new = tail | (val & _Q_LOCKED_MASK); + new = tail | (val & _Q_LOCKED_PENDING_MASK); old =atomic_cmpxchg (&lock->val, val, new);if (old == val)@@ -145,7 +211,7 @@ void queue_spin_lock_slowpath(struct qsp/* * if there was a previous node; link it and wait. */ -if (old & ~_Q_LOCKED_MASK) { +if (old & ~_Q_LOCKED_PENDING_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;@@ -153,18 +219,19 @@ void queue_spin_lock_slowpath(struct qsp }/* -* we're at the head of the waitqueue, wait for the owner to go away. +* we're at the head of the waitqueue, wait for the owner & pending to +* go away. * -* *,x -> *,0 +* *,x,y -> *,0,0 */ -while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +while ((val =atomic_read (&lock->val)) & _Q_LOCKED_PENDING_MASK)cpu_relax ();/* * claim the lock: * -* n,0 -> 0,1 : lock, uncontended -* *,0 -> *,1 : lock, contended +* n,0,0 -> 0,0,1 : lock, uncontended +* *,0,0 -> *,0,1 : lock, contended */ for (;;) { new = _Q_LOCKED_VAL;
PATCH 3/11 qspinlock: Add pending bit
In reply to Peter ZijlstraBecause the qspinlock needs to touch a second cacheline; add a pending bit and allow a single in-word spinner before we punt to the second cacheline.
--- include/asm-generic/qspinlock_types.h | 12 ++- kernel/locking/qspinlock.c | 109 +++++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 24 deletions(-)
include/asm-generic/qspinlock_types.h +9 -3
@@ -39,8 +39,9 @@ typedef struct qspinlock {* Bitfields in the atomic value: * * 0- 7: locked byte -* 8- 9: tail index -* 10-31: tail cpu (+1) +* 8: pending +* 9-10: tail index +* 11-31: tail cpu (+1) */ #define _Q_SET_MASK (type) (((1U << _Q_ ## type ## _BITS) - 1)\ << _Q_ ## type ## _OFFSET)
@@ -48,7 +49,11 @@ typedef struct qspinlock {#define _Q_LOCKED_BITS 8#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED) -#define _Q_TAIL_IDX_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#define _Q_PENDING_BITS 1 +#define _Q_PENDING_MASK _Q_SET_MASK(PENDING) + +#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)#define _Q_TAIL_IDX_BITS 2#define _Q_TAIL_IDX_MASK _Q_SET_MASK(TAIL_IDX)
@@ -57,5 +62,6 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU)#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET) +#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)#endif /* __ASM_GENERIC_QSPINLOCK_TYPES_H */
kernel/locking/qspinlock.c +88 -21
158 lines not on this page · raw message
PATCH 4/11 qspinlock: Extract out the exchange of tail code word
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new xchg_tail() function which can be optimized in a later patch.
--- include/asm-generic/qspinlock_types.h | 2 + kernel/locking/qspinlock.c | 58 +++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 22 deletions(-)
include/asm-generic/qspinlock_types.h +2 -0
@@ -61,6 +61,8 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK) +#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)
kernel/locking/qspinlock.c +36 -22
78 lines not on this page · raw message
Re: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
Quoted 5 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new
xchg_tail()function which can be optimized in a later patch.
And also adds a third try on acquiring the lock. That I think should be a seperate patch.
And instead of saying 'later patch' you should spell out the name of the patch. Especially as this might not be obvious from somebody doing git bisection.
Quoted 71 lines
Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>--- include/asm-generic/qspinlock_types.h | 2 + kernel/locking/qspinlock.c | 58 +++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 22 deletions(-)include/asm-generic/qspinlock_types.h +2
-0@@ -61,6 +61,8 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK) +#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)kernel/locking/qspinlock.c +28
-10@@ -86,6 +86,31 @@ static inline struct mcs_spinlock *decod#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK)/** +* xchg_tail - Put in the new queue tail code word & retrieve previous one +* @lock : Pointer to queue spinlock structure +* @tail : The new queue tail code word +* Return: The previous queue tail code word +* +* xchg(lock, tail) +* +* p,*,* -> n,*,* ; prev = xchg(lock, node) +*/ +static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail) +{ +u32 old, new, val =atomic_read (&lock->val); + +for (;;) { + new = (val & _Q_LOCKED_PENDING_MASK) | tail; + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +return old; +} + +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word @@ -182,36 +207,25 @@ void queue_spin_lock_slowpath(struct qsp node->next =NULL ;/* -* we already touched the queueing cacheline; don't bother with pending -* stuff. -* -* trylock || xchg(lock, node) -* -* 0,0,0 -> 0,0,1 ; trylock -* p,y,x -> n,y,x ; prev = xchg(lock, node) +* We touched a (possibly) cold cacheline in the per-cpu queue node; +* attempt the trylock once more in the hope someone let go while we +* weren't watching. */ -for (;;) { - new = _Q_LOCKED_VAL; -if (val)
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 215
Quoted 10 lines
kernel/locking/qspinlock.c +2
-8- new = tail | (val & _Q_LOCKED_PENDING_MASK); - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +if (queue_spin_trylock (lock)) + goto release;
So now are three of them? One in queue_spin_lock, then at the start of this function when checking for the pending bit, and the once more here. And that is because the local cache line might be cold for the 'mcs_index' struct?
That all seems to be a bit of experimental. But then we are already in the slowpath so we could as well do:
for (i = 0; i < 10; i++)
if (queue_spin_trylock(lock)) goto release;
And would have the same effect.
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 219
Quoted 5 lines
kernel/locking/qspinlock.c +2
-1/* -* we won the trylock; forget about queueing. +* we already touched the queueing cacheline; don't bother with pending +* stuff.
I guess we could also just erase the pending bit if we wanted too. The optimistic spinning will still hit go to the queue label as lock->val will have the tail value.
Quoted 17 lines
kernel/locking/qspinlock.c +4
-3+* +* p,*,* -> n,*,* */ -if (new == _Q_LOCKED_VAL) - goto release; + old =xchg_tail (lock, tail);/* * if there was a previous node; link it and wait. */ -if (old & ~_Q_LOCKED_PENDING_MASK) { +if (old & _Q_TAIL_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;
Paolo Bonzini
maintainerRe: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
Il 17/06/2014 22:55, Konrad Rzeszutek Wilk ha scritto:
Quoted 9 lines · Peter Zijlstra
Quoted 5 lines
From: Waiman Long <[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new
xchg_tail()function which can be optimized in a later patch.And also adds a third try on acquiring the lock. That I think should be a seperate patch.
It doesn't really add a new try, the old code is:
- for (;;) {
- new = _Q_LOCKED_VAL;
- if (val)
- new = tail | (val & _Q_LOCKED_PENDING_MASK);
-
- old = atomic_cmpxchg(&lock->val, val, new);
- if (old == val)
- break;
-
- val = old;
- }
/*
- * we won the trylock; forget about queueing.
*/
- if (new == _Q_LOCKED_VAL)
- goto release;
The trylock happens if the "if (val)" hits the else branch.
What the patch does is change it from attempting two transition with a single cmpxchg:
- * 0,0,0 -> 0,0,1 ; trylock
- * p,y,x -> n,y,x ; prev = xchg(lock, node)
to first doing the trylock, then the xchg. If the trylock passes and the xchg returns prev=0,0,0, the next step of the algorithm goes to the locked/uncontended state
+ /*
+ * claim the lock:
+ *
+ * n,0 -> 0,1 : lock, uncontended
Similar to your suggestion of patch 3, it's expected that the xchg will *not* return prev=0,0,0 after a failed trylock.
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
Paolo
Quoted 128 lines
And instead of saying 'later patch' you should spell out the name of the patch. Especially as this might not be obvious from somebody doing git bisection.
Quoted 71 lines
Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>--- include/asm-generic/qspinlock_types.h | 2 + kernel/locking/qspinlock.c | 58 +++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 22 deletions(-)include/asm-generic/qspinlock_types.h +2
-0@@ -61,6 +61,8 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK) +#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)kernel/locking/qspinlock.c +28
-10@@ -86,6 +86,31 @@ static inline struct mcs_spinlock *decod#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK)/** +* xchg_tail - Put in the new queue tail code word & retrieve previous one +* @lock : Pointer to queue spinlock structure +* @tail : The new queue tail code word +* Return: The previous queue tail code word +* +* xchg(lock, tail) +* +* p,*,* -> n,*,* ; prev = xchg(lock, node) +*/ +static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail) +{ +u32 old, new, val =atomic_read (&lock->val); + +for (;;) { + new = (val & _Q_LOCKED_PENDING_MASK) | tail; + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +return old; +} + +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word @@ -182,36 +207,25 @@ void queue_spin_lock_slowpath(struct qsp node->next =NULL ;/* -* we already touched the queueing cacheline; don't bother with pending -* stuff. -* -* trylock || xchg(lock, node) -* -* 0,0,0 -> 0,0,1 ; trylock -* p,y,x -> n,y,x ; prev = xchg(lock, node) +* We touched a (possibly) cold cacheline in the per-cpu queue node; +* attempt the trylock once more in the hope someone let go while we +* weren't watching. */ -for (;;) { - new = _Q_LOCKED_VAL; -if (val)kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 215
Quoted 10 lines
kernel/locking/qspinlock.c +2
-8- new = tail | (val & _Q_LOCKED_PENDING_MASK); - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +if (queue_spin_trylock (lock)) + goto release;So now are three of them? One in
queue_spin_lock, then at the start of this function when checking for the pending bit, and the once more here. And that is because the local cache line might be cold for the 'mcs_index' struct?That all seems to be a bit of experimental. But then we are already in the slowpath so we could as well do:
for (i = 0; i < 10; i++)
if (queue_spin_trylock(lock)) goto release;And would have the same effect.
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 219
Quoted 5 lines
kernel/locking/qspinlock.c +2
-1/* -* we won the trylock; forget about queueing. +* we already touched the queueing cacheline; don't bother with pending +* stuff. I guess we could also just erase the pending bit if we wanted too. The optimistic spinning will still hit go to the queue label as
lock->valwill have the tail value.Quoted 17 lines
kernel/locking/qspinlock.c +4
-3+* +* p,*,* -> n,*,* */ -if (new == _Q_LOCKED_VAL) - goto release; + old =xchg_tail (lock, tail);/* * if there was a previous node; link it and wait. */ -if (old & ~_Q_LOCKED_PENDING_MASK) { +if (old & _Q_TAIL_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;
Re: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
Quoted 49 lines · Paolo Bonzini
Il 17/06/2014 22:55, Konrad Rzeszutek Wilk ha scritto:
Quoted 9 lines · Peter Zijlstra
Quoted 5 lines
From: Waiman Long <[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new
xchg_tail()function which can be optimized in a later patch.And also adds a third try on acquiring the lock. That I think should be a seperate patch.
It doesn't really add a new try, the old code is:
kernel/locking/qspinlock.c +0
-14-for (;;) { - new = _Q_LOCKED_VAL; -if (val) - new = tail | (val & _Q_LOCKED_PENDING_MASK); - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - }/* -* we won the trylock; forget about queueing. */ -if (new == _Q_LOCKED_VAL) - goto release;The trylock happens if the "if (val)" hits the else branch.
What the patch does is change it from attempting two transition with a single cmpxchg:
kernel/locking/qspinlock.c +0
-2-* 0,0,0 -> 0,0,1 ; trylock -* p,y,x -> n,y,x ; prev = xchg(lock, node) to first doing the trylock, then the xchg. If the trylock passes and the xchg returns prev=0,0,0, the next step of the algorithm goes to the locked/uncontended state
(quoted diff) +4
-0+ /* + * claim the lock: + * + * n,0 -> 0,1 : lock, uncontended
Quoted 2 lines
Similar to your suggestion of patch 3, it's expected that the xchg will *not* return prev=0,0,0 after a failed trylock.
I do like your explanation. I hope that Peter will put it in the description as it explains the change quite well.
Quoted 3 lines
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
Uh, did I say that? Oh I said why don't make it right the first time!
I meant in terms of seperating the slowpath (aka the bytelock on the pending bit) from the queue (MCS code). Or renaming the function to be called 'complex' instead of 'slowpath' as it is getting quite hairy.
The #1 patch is nice by itself - as it lays out the foundation of the MCS-similar code - and if Ingo decides he does not want this pending byte-lock bit business - it can be easily reverted or dropped.
In terms of squashing this in #1 - I would advocate against that.
Thanks!
Re: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
Quoted 57 lines · Konrad Rzeszutek Wilk
Quoted 47 lines · Paolo Bonzini
Il 17/06/2014 22:55, Konrad Rzeszutek Wilk ha scritto:
Quoted 8 lines · Peter Zijlstra
Quoted 5 lines
From: Waiman Long<[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new
xchg_tail()function which can be optimized in a later patch.And also adds a third try on acquiring the lock. That I think should be a seperate patch.
It doesn't really add a new try, the old code is:
kernel/locking/qspinlock.c +0
-14-for (;;) { - new = _Q_LOCKED_VAL; -if (val) - new = tail | (val& _Q_LOCKED_PENDING_MASK); - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - }/* -* we won the trylock; forget about queueing. */ -if (new == _Q_LOCKED_VAL) - goto release;The trylock happens if the "if (val)" hits the else branch.
What the patch does is change it from attempting two transition with a single cmpxchg:
kernel/locking/qspinlock.c +0
-2-* 0,0,0 -> 0,0,1 ; trylock -* p,y,x -> n,y,x ; prev = xchg(lock, node) to first doing the trylock, then the xchg. If the trylock passes and the xchg returns prev=0,0,0, the next step of the algorithm goes to the locked/uncontended state
(quoted diff) +4
-0+ /* + * claim the lock: + * + * n,0 -> 0,1 : lock, uncontendedQuoted 2 lines
Similar to your suggestion of patch 3, it's expected that the xchg will *not* return prev=0,0,0 after a failed trylock.
I do like your explanation. I hope that Peter will put it in the description as it explains the change quite well.
Quoted 2 lines
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
Uh, did I say that? Oh I said why don't make it right the first time!
Quoted 7 lines
I meant in terms of seperating the slowpath (aka the bytelock on the pending bit) from the queue (MCS code). Or renaming the function to be called 'complex' instead of 'slowpath' as it is getting quite hairy.
The #1 patch is nice by itself - as it lays out the foundation of the MCS-similar code - and if Ingo decides he does not want this pending byte-lock bit business - it can be easily reverted or dropped.
The pending bit code is needed for performance parity with ticket spinlock for light load. My own measurement indicates that the queuing overhead will cause the queue spinlock to be slower than ticket spinlock with 2-4 contending tasks. The pending bit solves the performance problem with 2 contending tasks, leave only the 3-4 tasks cases being a bit slower than the ticket spinlock which should be more than compensated by its superior performance with heavy contention and slightly better performance with no contention.
-Longman
Paolo Bonzini
maintainerRe: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
Il 18/06/2014 17:46, Waiman Long ha scritto:
Quoted 6 lines
Quoted 5 lines
The #1 patch is nice by itself - as it lays out the foundation of the MCS-similar code - and if Ingo decides he does not want this pending byte-lock bit business - it can be easily reverted or dropped.
Quoted 8 lines
The pending bit code is needed for performance parity with ticket spinlock for light load. My own measurement indicates that the queuing overhead will cause the queue spinlock to be slower than ticket spinlock with 2-4 contending tasks. The pending bit solves the performance problem with 2 contending tasks, leave only the 3-4 tasks cases being a bit slower than the ticket spinlock which should be more than compensated by its superior performance with heavy contention and slightly better performance with no contention.
Note that this patch is not related to the pending bit, only to the trylock bit which is already in patch 1. It serializes two previously-parallel checks for transitions. This is why I thought it could already belong in patch 1.
Paolo
Paolo Bonzini
maintainerRe: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
In reply to Waiman LongIl 18/06/2014 17:46, Waiman Long ha scritto:
Quoted 6 lines
Quoted 5 lines
The #1 patch is nice by itself - as it lays out the foundation of the MCS-similar code - and if Ingo decides he does not want this pending byte-lock bit business - it can be easily reverted or dropped.
Quoted 8 lines
The pending bit code is needed for performance parity with ticket spinlock for light load. My own measurement indicates that the queuing overhead will cause the queue spinlock to be slower than ticket spinlock with 2-4 contending tasks. The pending bit solves the performance problem with 2 contending tasks, leave only the 3-4 tasks cases being a bit slower than the ticket spinlock which should be more than compensated by its superior performance with heavy contention and slightly better performance with no contention.
Note that this patch is not related to the pending bit, only to the trylock bit which is already in patch 1. It serializes two previously-parallel checks for transitions. This is why I thought it could already belong in patch 1.
Paolo
Re: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
In reply to Waiman LongQuoted 8 lines
Quoted 8 lines
Quoted 2 lines
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
Uh, did I say that? Oh I said why don't make it right the first time!
I meant in terms of seperating the slowpath (aka the bytelock on the pending bit) from the queue (MCS code). Or renaming the function to be called 'complex' instead of 'slowpath' as it is getting quite hairy.
Quoted 8 lines
Quoted 3 lines
The #1 patch is nice by itself - as it lays out the foundation of the MCS-similar code - and if Ingo decides he does not want this pending byte-lock bit business - it can be easily reverted or dropped.
The pending bit code is needed for performance parity with ticket spinlock for light load. My own measurement indicates that the queuing overhead will cause the queue spinlock to be slower than ticket spinlock with 2-4 contending tasks. The pending bit solves the performance problem with 2
Aha!
Quoted 4 lines
contending tasks, leave only the 3-4 tasks cases being a bit slower than the ticket spinlock which should be more than compensated by its superior performance with heavy contention and slightly better performance with no contention.
That should be mentioned in the commit description as the rationale for the patch "qspinlock: Add pending bit" and also in the code.
Thank you!
Re: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
In reply to Waiman LongQuoted 8 lines
Quoted 8 lines
Quoted 2 lines
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
Uh, did I say that? Oh I said why don't make it right the first time!
I meant in terms of seperating the slowpath (aka the bytelock on the pending bit) from the queue (MCS code). Or renaming the function to be called 'complex' instead of 'slowpath' as it is getting quite hairy.
Quoted 8 lines
Quoted 3 lines
The #1 patch is nice by itself - as it lays out the foundation of the MCS-similar code - and if Ingo decides he does not want this pending byte-lock bit business - it can be easily reverted or dropped.
The pending bit code is needed for performance parity with ticket spinlock for light load. My own measurement indicates that the queuing overhead will cause the queue spinlock to be slower than ticket spinlock with 2-4 contending tasks. The pending bit solves the performance problem with 2
Aha!
Quoted 4 lines
contending tasks, leave only the 3-4 tasks cases being a bit slower than the ticket spinlock which should be more than compensated by its superior performance with heavy contention and slightly better performance with no contention.
That should be mentioned in the commit description as the rationale for the patch "qspinlock: Add pending bit" and also in the code.
Thank you!
Re: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
In reply to Konrad Rzeszutek WilkQuoted 57 lines · Konrad Rzeszutek Wilk
Quoted 47 lines · Paolo Bonzini
Il 17/06/2014 22:55, Konrad Rzeszutek Wilk ha scritto:
Quoted 8 lines · Peter Zijlstra
Quoted 5 lines
From: Waiman Long<[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new
xchg_tail()function which can be optimized in a later patch.And also adds a third try on acquiring the lock. That I think should be a seperate patch.
It doesn't really add a new try, the old code is:
kernel/locking/qspinlock.c +0
-14-for (;;) { - new = _Q_LOCKED_VAL; -if (val) - new = tail | (val& _Q_LOCKED_PENDING_MASK); - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - }/* -* we won the trylock; forget about queueing. */ -if (new == _Q_LOCKED_VAL) - goto release;The trylock happens if the "if (val)" hits the else branch.
What the patch does is change it from attempting two transition with a single cmpxchg:
kernel/locking/qspinlock.c +0
-2-* 0,0,0 -> 0,0,1 ; trylock -* p,y,x -> n,y,x ; prev = xchg(lock, node) to first doing the trylock, then the xchg. If the trylock passes and the xchg returns prev=0,0,0, the next step of the algorithm goes to the locked/uncontended state
(quoted diff) +4
-0+ /* + * claim the lock: + * + * n,0 -> 0,1 : lock, uncontendedQuoted 2 lines
Similar to your suggestion of patch 3, it's expected that the xchg will *not* return prev=0,0,0 after a failed trylock.
I do like your explanation. I hope that Peter will put it in the description as it explains the change quite well.
Quoted 2 lines
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
Uh, did I say that? Oh I said why don't make it right the first time!
Quoted 7 lines
I meant in terms of seperating the slowpath (aka the bytelock on the pending bit) from the queue (MCS code). Or renaming the function to be called 'complex' instead of 'slowpath' as it is getting quite hairy.
The #1 patch is nice by itself - as it lays out the foundation of the MCS-similar code - and if Ingo decides he does not want this pending byte-lock bit business - it can be easily reverted or dropped.
The pending bit code is needed for performance parity with ticket spinlock for light load. My own measurement indicates that the queuing overhead will cause the queue spinlock to be slower than ticket spinlock with 2-4 contending tasks. The pending bit solves the performance problem with 2 contending tasks, leave only the 3-4 tasks cases being a bit slower than the ticket spinlock which should be more than compensated by its superior performance with heavy contention and slightly better performance with no contention.
-Longman
Re: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
In reply to Paolo BonziniQuoted 49 lines · Paolo Bonzini
Il 17/06/2014 22:55, Konrad Rzeszutek Wilk ha scritto:
Quoted 9 lines · Peter Zijlstra
Quoted 5 lines
From: Waiman Long <[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new
xchg_tail()function which can be optimized in a later patch.And also adds a third try on acquiring the lock. That I think should be a seperate patch.
It doesn't really add a new try, the old code is:
kernel/locking/qspinlock.c +0
-14-for (;;) { - new = _Q_LOCKED_VAL; -if (val) - new = tail | (val & _Q_LOCKED_PENDING_MASK); - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - }/* -* we won the trylock; forget about queueing. */ -if (new == _Q_LOCKED_VAL) - goto release;The trylock happens if the "if (val)" hits the else branch.
What the patch does is change it from attempting two transition with a single cmpxchg:
kernel/locking/qspinlock.c +0
-2-* 0,0,0 -> 0,0,1 ; trylock -* p,y,x -> n,y,x ; prev = xchg(lock, node) to first doing the trylock, then the xchg. If the trylock passes and the xchg returns prev=0,0,0, the next step of the algorithm goes to the locked/uncontended state
(quoted diff) +4
-0+ /* + * claim the lock: + * + * n,0 -> 0,1 : lock, uncontended
Quoted 2 lines
Similar to your suggestion of patch 3, it's expected that the xchg will *not* return prev=0,0,0 after a failed trylock.
I do like your explanation. I hope that Peter will put it in the description as it explains the change quite well.
Quoted 3 lines
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
Uh, did I say that? Oh I said why don't make it right the first time!
I meant in terms of seperating the slowpath (aka the bytelock on the pending bit) from the queue (MCS code). Or renaming the function to be called 'complex' instead of 'slowpath' as it is getting quite hairy.
The #1 patch is nice by itself - as it lays out the foundation of the MCS-similar code - and if Ingo decides he does not want this pending byte-lock bit business - it can be easily reverted or dropped.
In terms of squashing this in #1 - I would advocate against that.
Thanks!
Peter Zijlstra
maintainerRe: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
In reply to Paolo BonziniQuoted 2 lines · Paolo Bonzini
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
So I explicitly broke out these optimizations into separate patches so that we can see them independently and agree they're idempotent wrt the state machine.
The initial patches by Waiman were totally unreadable, partly because the optimizations made the code terribly complex.
Luckily waiman then dropped the most horrible optimizations (optimization for the very large nr_cpus case, were we cannot have a pending byte), so the end result isn't quite as complex as it used to be.
Peter Zijlstra
maintainerRe: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
In reply to Paolo BonziniQuoted 2 lines · Paolo Bonzini
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
So I explicitly broke out these optimizations into separate patches so that we can see them independently and agree they're idempotent wrt the state machine.
The initial patches by Waiman were totally unreadable, partly because the optimizations made the code terribly complex.
Luckily waiman then dropped the most horrible optimizations (optimization for the very large nr_cpus case, were we cannot have a pending byte), so the end result isn't quite as complex as it used to be.
Paolo Bonzini
maintainerRe: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
In reply to Konrad Rzeszutek WilkIl 17/06/2014 22:55, Konrad Rzeszutek Wilk ha scritto:
Quoted 9 lines · Peter Zijlstra
Quoted 5 lines
From: Waiman Long <[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new
xchg_tail()function which can be optimized in a later patch.And also adds a third try on acquiring the lock. That I think should be a seperate patch.
It doesn't really add a new try, the old code is:
- for (;;) {
- new = _Q_LOCKED_VAL;
- if (val)
- new = tail | (val & _Q_LOCKED_PENDING_MASK);
-
- old = atomic_cmpxchg(&lock->val, val, new);
- if (old == val)
- break;
-
- val = old;
- }
/*
- * we won the trylock; forget about queueing.
*/
- if (new == _Q_LOCKED_VAL)
- goto release;
The trylock happens if the "if (val)" hits the else branch.
What the patch does is change it from attempting two transition with a single cmpxchg:
- * 0,0,0 -> 0,0,1 ; trylock
- * p,y,x -> n,y,x ; prev = xchg(lock, node)
to first doing the trylock, then the xchg. If the trylock passes and the xchg returns prev=0,0,0, the next step of the algorithm goes to the locked/uncontended state
+ /*
+ * claim the lock:
+ *
+ * n,0 -> 0,1 : lock, uncontended
Similar to your suggestion of patch 3, it's expected that the xchg will *not* return prev=0,0,0 after a failed trylock.
However, I *do* agree with you that it's simpler to just squash this patch into 01/11.
Paolo
Quoted 128 lines
And instead of saying 'later patch' you should spell out the name of the patch. Especially as this might not be obvious from somebody doing git bisection.
Quoted 71 lines
Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>--- include/asm-generic/qspinlock_types.h | 2 + kernel/locking/qspinlock.c | 58 +++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 22 deletions(-)include/asm-generic/qspinlock_types.h +2
-0@@ -61,6 +61,8 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK) +#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)kernel/locking/qspinlock.c +28
-10@@ -86,6 +86,31 @@ static inline struct mcs_spinlock *decod#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK)/** +* xchg_tail - Put in the new queue tail code word & retrieve previous one +* @lock : Pointer to queue spinlock structure +* @tail : The new queue tail code word +* Return: The previous queue tail code word +* +* xchg(lock, tail) +* +* p,*,* -> n,*,* ; prev = xchg(lock, node) +*/ +static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail) +{ +u32 old, new, val =atomic_read (&lock->val); + +for (;;) { + new = (val & _Q_LOCKED_PENDING_MASK) | tail; + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +return old; +} + +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word @@ -182,36 +207,25 @@ void queue_spin_lock_slowpath(struct qsp node->next =NULL ;/* -* we already touched the queueing cacheline; don't bother with pending -* stuff. -* -* trylock || xchg(lock, node) -* -* 0,0,0 -> 0,0,1 ; trylock -* p,y,x -> n,y,x ; prev = xchg(lock, node) +* We touched a (possibly) cold cacheline in the per-cpu queue node; +* attempt the trylock once more in the hope someone let go while we +* weren't watching. */ -for (;;) { - new = _Q_LOCKED_VAL; -if (val)kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 215
Quoted 10 lines
kernel/locking/qspinlock.c +2
-8- new = tail | (val & _Q_LOCKED_PENDING_MASK); - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +if (queue_spin_trylock (lock)) + goto release;So now are three of them? One in
queue_spin_lock, then at the start of this function when checking for the pending bit, and the once more here. And that is because the local cache line might be cold for the 'mcs_index' struct?That all seems to be a bit of experimental. But then we are already in the slowpath so we could as well do:
for (i = 0; i < 10; i++)
if (queue_spin_trylock(lock)) goto release;And would have the same effect.
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 219
Quoted 5 lines
kernel/locking/qspinlock.c +2
-1/* -* we won the trylock; forget about queueing. +* we already touched the queueing cacheline; don't bother with pending +* stuff. I guess we could also just erase the pending bit if we wanted too. The optimistic spinning will still hit go to the queue label as
lock->valwill have the tail value.Quoted 17 lines
kernel/locking/qspinlock.c +4
-3+* +* p,*,* -> n,*,* */ -if (new == _Q_LOCKED_VAL) - goto release; + old =xchg_tail (lock, tail);/* * if there was a previous node; link it and wait. */ -if (old & ~_Q_LOCKED_PENDING_MASK) { +if (old & _Q_TAIL_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;
Re: [PATCH 04/11] qspinlock: Extract out the exchange of tail code word
In reply to Peter ZijlstraQuoted 5 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new
xchg_tail()function which can be optimized in a later patch.
And also adds a third try on acquiring the lock. That I think should be a seperate patch.
And instead of saying 'later patch' you should spell out the name of the patch. Especially as this might not be obvious from somebody doing git bisection.
Quoted 71 lines
Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>--- include/asm-generic/qspinlock_types.h | 2 + kernel/locking/qspinlock.c | 58 +++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 22 deletions(-)include/asm-generic/qspinlock_types.h +2
-0@@ -61,6 +61,8 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK) +#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)kernel/locking/qspinlock.c +28
-10@@ -86,6 +86,31 @@ static inline struct mcs_spinlock *decod#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK)/** +* xchg_tail - Put in the new queue tail code word & retrieve previous one +* @lock : Pointer to queue spinlock structure +* @tail : The new queue tail code word +* Return: The previous queue tail code word +* +* xchg(lock, tail) +* +* p,*,* -> n,*,* ; prev = xchg(lock, node) +*/ +static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail) +{ +u32 old, new, val =atomic_read (&lock->val); + +for (;;) { + new = (val & _Q_LOCKED_PENDING_MASK) | tail; + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +return old; +} + +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word @@ -182,36 +207,25 @@ void queue_spin_lock_slowpath(struct qsp node->next =NULL ;/* -* we already touched the queueing cacheline; don't bother with pending -* stuff. -* -* trylock || xchg(lock, node) -* -* 0,0,0 -> 0,0,1 ; trylock -* p,y,x -> n,y,x ; prev = xchg(lock, node) +* We touched a (possibly) cold cacheline in the per-cpu queue node; +* attempt the trylock once more in the hope someone let go while we +* weren't watching. */ -for (;;) { - new = _Q_LOCKED_VAL; -if (val)
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 215
Quoted 10 lines
kernel/locking/qspinlock.c +2
-8- new = tail | (val & _Q_LOCKED_PENDING_MASK); - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +if (queue_spin_trylock (lock)) + goto release;
So now are three of them? One in queue_spin_lock, then at the start of this function when checking for the pending bit, and the once more here. And that is because the local cache line might be cold for the 'mcs_index' struct?
That all seems to be a bit of experimental. But then we are already in the slowpath so we could as well do:
for (i = 0; i < 10; i++)
if (queue_spin_trylock(lock)) goto release;
And would have the same effect.
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 219
Quoted 5 lines
kernel/locking/qspinlock.c +2
-1/* -* we won the trylock; forget about queueing. +* we already touched the queueing cacheline; don't bother with pending +* stuff.
I guess we could also just erase the pending bit if we wanted too. The optimistic spinning will still hit go to the queue label as lock->val will have the tail value.
Quoted 17 lines
kernel/locking/qspinlock.c +4
-3+* +* p,*,* -> n,*,* */ -if (new == _Q_LOCKED_VAL) - goto release; + old =xchg_tail (lock, tail);/* * if there was a previous node; link it and wait. */ -if (old & ~_Q_LOCKED_PENDING_MASK) { +if (old & _Q_TAIL_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;
PATCH 4/11 qspinlock: Extract out the exchange of tail code word
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new xchg_tail() function which can be optimized in a later patch.
--- include/asm-generic/qspinlock_types.h | 2 + kernel/locking/qspinlock.c | 58 +++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 22 deletions(-)
include/asm-generic/qspinlock_types.h +2 -0
@@ -61,6 +61,8 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK) +#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)
kernel/locking/qspinlock.c +36 -22
77 lines not on this page · raw message
PATCH 4/11 qspinlock: Extract out the exchange of tail code word
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch extracts the logic for the exchange of new and previous tail code words into a new xchg_tail() function which can be optimized in a later patch.
--- include/asm-generic/qspinlock_types.h | 2 + kernel/locking/qspinlock.c | 58 +++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 22 deletions(-)
include/asm-generic/qspinlock_types.h +2 -0
@@ -61,6 +61,8 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK) +#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)
kernel/locking/qspinlock.c +36 -22
77 lines not on this page · raw message
PATCH 5/11 qspinlock: Optimize for smaller NR_CPUS
In reply to Peter ZijlstraFrom: Peter Zijlstra <[email protected]>
When we allow for a max NR_CPUS < 2^14 we can optimize the pending wait-acquire and the xchg_tail() operations.
By growing the pending bit to a byte, we reduce the tail to 16bit. This means we can use xchg16 for the tail part and do away with all the repeated compxchg() operations.
This in turn allows us to unconditionally acquire; the locked state as observed by the wait loops cannot change. And because both locked and pending are now a full byte we can use simple stores for the state transition, obviating one atomic operation entirely.
All this is horribly broken on Alpha pre EV56 (and any other arch that cannot do single-copy atomic byte stores).
--- include/asm-generic/qspinlock_types.h | 13 ++++ kernel/locking/qspinlock.c | 103 ++++++++++++++++++++++++++++++---- 2 files changed, 106 insertions(+), 10 deletions(-)
include/asm-generic/qspinlock_types.h +13 -0
@@ -38,6 +38,14 @@ typedef struct qspinlock {/* * Bitfields in the atomic value: * +* When NR_CPUS < 16K +* 0- 7: locked byte +* 8: pending +* 9-15: not used +* 16-17: tail index +* 18-31: tail cpu (+1) +* +* When NR_CPUS >= 16K * 0- 7: locked byte * 8: pending * 9-10: tail index
@@ -50,7 +58,11 @@ typedef struct qspinlock {#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED)#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#if CONFIG_NR_CPUS < (1U <<14 ) +#define _Q_PENDING_BITS 8 +#else #define _Q_PENDING_BITS 1 +#endif #define _Q_PENDING_MASK _Q_SET_MASK(PENDING)#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)
@@ -61,6 +73,7 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_OFFSET _Q_TAIL_IDX_OFFSET#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK)#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)
kernel/locking/qspinlock.c +93 -10
140 lines not on this page · raw message
Paolo Bonzini
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
Il 15/06/2014 14:47, Peter Zijlstra ha scritto:
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 277
Quoted 12 lines
kernel/locking/qspinlock.c +1
-9-for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);return ;
Might as well add clear_pending_set_locked already in patch 3.
Paolo
Peter Zijlstra
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
Quoted 16 lines · Paolo Bonzini
Il 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 11 lines
kernel/locking/qspinlock.c +1
-9-for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);Quoted 1 line
return;Might as well add
clear_pending_set_lockedalready in patch 3.
Patch 4, if anywhere.
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Paolo Bonzini
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
Il 07/07/2014 16:35, Peter Zijlstra ha scritto:
Quoted 18 lines · Paolo Bonzini
Quoted 16 lines
Il 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 11 lines
kernel/locking/qspinlock.c +1
-9-for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);Quoted 1 line
return;Might as well add
clear_pending_set_lockedalready in patch 3.
Quoted 2 lines
Patch 4, if anywhere.
This code is added in patch 3:
+ /*
+ * take ownership and clear the pending bit.
+ *
+ * *,1,0 -> *,0,1
+ */
+ for (;;) {
+ new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL;
+
+ old = atomic_cmpxchg(&lock->val, val, new);
+ if (old == val)
+ break;
+
+ val = old;
+ }
+ return;
Unlike the change in patch 4, clear_pending_set_locked doesn't change how qspinlock moves from a state to the next.
Paolo
Peter Zijlstra
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
Quoted 40 lines · Paolo Bonzini
Il 07/07/2014 16:35, Peter Zijlstra ha scritto:
Quoted 18 lines · Paolo Bonzini
Quoted 16 lines
Il 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 11 lines
kernel/locking/qspinlock.c +1
-9-for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);Quoted 1 line
return;Might as well add
clear_pending_set_lockedalready in patch 3.Quoted 2 lines
Patch 4, if anywhere.
This code is added in patch 3:
kernel/locking/qspinlock.c +15
-0+/* +* take ownership and clear the pending bit. +* +* *,1,0 -> *,0,1 +*/ +for (;;) { + new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +return ;
Quoted 2 lines
Unlike the change in patch 4,
clear_pending_set_lockeddoesn't change how qspinlock moves from a state to the next.
True, but its where we start to break up into smaller functions. And the only reason we break them out is because we'll get different implementations depending on NR_CPUS.
So we can view the breakout of xchg_tail and clear_and_set_pending as preparatory work for introducing the NR_CPUS thing.
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Paolo Bonzini
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
Il 07/07/2014 17:35, Peter Zijlstra ha scritto:
Quoted 8 lines
Quoted 2 lines
Quoted 2 lines
Unlike the change in patch 4,
clear_pending_set_lockeddoesn't change how qspinlock moves from a state to the next.True, but its where we start to break up into smaller functions. And the only reason we break them out is because we'll get different implementations depending on
NR_CPUS.So we can view the breakout of
xchg_tailandclear_and_set_pendingas preparatory work for introducing theNR_CPUSthing.
That's a different reasoning, but it's fine by me too.
Paolo
Paolo Bonzini
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
In reply to Peter ZijlstraIl 07/07/2014 17:35, Peter Zijlstra ha scritto:
Quoted 8 lines
Quoted 2 lines
Quoted 2 lines
Unlike the change in patch 4,
clear_pending_set_lockeddoesn't change how qspinlock moves from a state to the next.True, but its where we start to break up into smaller functions. And the only reason we break them out is because we'll get different implementations depending on
NR_CPUS.So we can view the breakout of
xchg_tailandclear_and_set_pendingas preparatory work for introducing theNR_CPUSthing.
That's a different reasoning, but it's fine by me too.
Paolo
Peter Zijlstra
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
In reply to Paolo BonziniQuoted 40 lines · Paolo Bonzini
Il 07/07/2014 16:35, Peter Zijlstra ha scritto:
Quoted 18 lines · Paolo Bonzini
Quoted 16 lines
Il 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 11 lines
kernel/locking/qspinlock.c +1
-9-for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);Quoted 1 line
return;Might as well add
clear_pending_set_lockedalready in patch 3.Quoted 2 lines
Patch 4, if anywhere.
This code is added in patch 3:
kernel/locking/qspinlock.c +15
-0+/* +* take ownership and clear the pending bit. +* +* *,1,0 -> *,0,1 +*/ +for (;;) { + new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +return ;
Quoted 2 lines
Unlike the change in patch 4,
clear_pending_set_lockeddoesn't change how qspinlock moves from a state to the next.
True, but its where we start to break up into smaller functions. And the only reason we break them out is because we'll get different implementations depending on NR_CPUS.
So we can view the breakout of xchg_tail and clear_and_set_pending as preparatory work for introducing the NR_CPUS thing.
_______________________________________________
Xen-devel mailing list
[email protected]
http://lists.xen.org/xen-devel
Paolo Bonzini
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
In reply to Peter ZijlstraIl 07/07/2014 16:35, Peter Zijlstra ha scritto:
Quoted 18 lines · Paolo Bonzini
Quoted 16 lines
Il 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 11 lines
kernel/locking/qspinlock.c +1
-9-for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);Quoted 1 line
return;Might as well add
clear_pending_set_lockedalready in patch 3.
Quoted 2 lines
Patch 4, if anywhere.
This code is added in patch 3:
+ /*
+ * take ownership and clear the pending bit.
+ *
+ * *,1,0 -> *,0,1
+ */
+ for (;;) {
+ new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL;
+
+ old = atomic_cmpxchg(&lock->val, val, new);
+ if (old == val)
+ break;
+
+ val = old;
+ }
+ return;
Unlike the change in patch 4, clear_pending_set_locked doesn't change how qspinlock moves from a state to the next.
Paolo
Paolo Bonzini
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
In reply to Peter ZijlstraIl 07/07/2014 16:35, Peter Zijlstra ha scritto:
Quoted 18 lines · Paolo Bonzini
Quoted 16 lines
Il 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 11 lines
kernel/locking/qspinlock.c +1
-9-for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);Quoted 1 line
return;Might as well add
clear_pending_set_lockedalready in patch 3.
Quoted 2 lines
Patch 4, if anywhere.
This code is added in patch 3:
+ /*
+ * take ownership and clear the pending bit.
+ *
+ * *,1,0 -> *,0,1
+ */
+ for (;;) {
+ new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL;
+
+ old = atomic_cmpxchg(&lock->val, val, new);
+ if (old == val)
+ break;
+
+ val = old;
+ }
+ return;
Unlike the change in patch 4, clear_pending_set_locked doesn't change how qspinlock moves from a state to the next.
Paolo
Peter Zijlstra
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
In reply to Paolo BonziniQuoted 16 lines · Paolo Bonzini
Il 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 11 lines
kernel/locking/qspinlock.c +1
-9-for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);Quoted 1 line
return;Might as well add
clear_pending_set_lockedalready in patch 3.
Patch 4, if anywhere.
_______________________________________________
Xen-devel mailing list
[email protected]
http://lists.xen.org/xen-devel
Paolo Bonzini
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
In reply to Peter ZijlstraIl 15/06/2014 14:47, Peter Zijlstra ha scritto:
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 277
Quoted 12 lines
kernel/locking/qspinlock.c +1
-9-for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);return ;
Might as well add clear_pending_set_locked already in patch 3.
Paolo
Re: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
In reply to Peter ZijlstraQuoted 5 lines · Peter Zijlstra
From: Peter Zijlstra <[email protected]>
When we allow for a max
NR_CPUS< 2^14 we can optimize the pending wait-acquire and thexchg_tail()operations.
Quoted 8 lines
By growing the pending bit to a byte, we reduce the tail to 16bit. This means we can use xchg16 for the tail part and do away with all the repeated
compxchg()operations.This in turn allows us to unconditionally acquire; the locked state as observed by the wait loops cannot change. And because both locked and pending are now a full byte we can use simple stores for the state transition, obviating one atomic operation entirely.
I have to ask - how much more performance do you get from this?
Is this extra atomic operation hurting that much?
Quoted 102 lines
All this is horribly broken on Alpha pre EV56 (and any other arch that cannot do single-copy atomic byte stores).
Signed-off-by Peter Zijlstra <[email protected]>--- include/asm-generic/qspinlock_types.h | 13 ++++ kernel/locking/qspinlock.c | 103 ++++++++++++++++++++++++++++++---- 2 files changed, 106 insertions(+), 10 deletions(-)include/asm-generic/qspinlock_types.h +13
-0@@ -38,6 +38,14 @@ typedef struct qspinlock {/* * Bitfields in the atomic value: * +* When NR_CPUS < 16K +* 0- 7: locked byte +* 8: pending +* 9-15: not used +* 16-17: tail index +* 18-31: tail cpu (+1) +* +* When NR_CPUS >= 16K * 0- 7: locked byte * 8: pending * 9-10: tail index @@ -50,7 +58,11 @@ typedef struct qspinlock {#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED)#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#if CONFIG_NR_CPUS < (1U <<14 ) +#define _Q_PENDING_BITS 8 +#else #define _Q_PENDING_BITS 1 +#endif #define _Q_PENDING_MASK _Q_SET_MASK(PENDING)#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)@@ -61,6 +73,7 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_OFFSET _Q_TAIL_IDX_OFFSET#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK)#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)kernel/locking/qspinlock.c +35
-0@@ -22,6 +22,7 @@#include <linux/percpu.h> #include <linux/hardirq.h> #include <linux/mutex.h> +#include <asm/byteorder.h> #include <asm/qspinlock.h> /* @@ -48,6 +49,9 @@* We can further change the first spinner to spin on a bit in the lock word * instead of its node; whereby avoiding the need to carry a node from lock to * unlock, and preserving API. +* +* N.B. The current implementation only supports architectures that allow +* atomic operations on smaller 8-bit and 16-bit data types. */ #include "mcs_spinlock.h" @@ -85,6 +89,87 @@ static inline struct mcs_spinlock *decod#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK) +/* +* By using the whole 2nd least significant byte for the pending bit, we +* can allow better optimization of the lock acquisition for the pending +* bit holder. +*/ +#if _Q_PENDING_BITS ==8 + +struct __qspinlock { +union { +atomic_t val; +struct { +#ifdef __LITTLE_ENDIAN +u16 locked_pending; +u16 tail; +#else +u16 tail; +u16 locked_pending; +#endif + }; + }; +}; + +/** +* clear_pending_set_locked - take ownership and clear the pending bit. +* @lock: Pointer to queue spinlock structure +* @val : Current value of the queue spinlock 32-bit word +* +* *,1,0 -> *,0,1 +* +* Lock stealing is not allowed if this function is used. +*/
kernel/locking/qspinlock.c static inline struct mcs_spinlock *decod · line 132
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+static __always_inline void +clear_pending_set_locked (struct qspinlock *lock,u32 val) +{ +struct __qspinlock *l = (void *)lock; + +ACCESS_ONCE (l->locked_pending) = _Q_LOCKED_VAL; +} + +/* +* xchg_tail - Put in the new queue tail code word & retrieve previous one
Missing full stop.
Quoted 53 lines
kernel/locking/qspinlock.c +41
-0+* @lock : Pointer to queue spinlock structure +* @tail : The new queue tail code word +* Return: The previous queue tail code word +* +* xchg(lock, tail) +* +* p,*,* -> n,*,* ; prev = xchg(lock, node) +*/ +static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail) +{ +struct __qspinlock *l = (void *)lock; + +return (u32 )xchg (&l->tail, tail >> _Q_TAIL_OFFSET) << _Q_TAIL_OFFSET; +} + +#else /* _Q_PENDING_BITS == 8 */ + +/** +* clear_pending_set_locked - take ownership and clear the pending bit. +* @lock: Pointer to queue spinlock structure +* @val : Current value of the queue spinlock 32-bit word +* +* *,1,0 -> *,0,1 +*/ +static __always_inline void +clear_pending_set_locked (struct qspinlock *lock,u32 val) +{ +u32 new, old; + +for (;;) { + new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +} +/** * xchg_tail - Put in the new queue tail code word & retrieve previous one * @lock : Pointer to queue spinlock structure @@ -109,6 +194,7 @@ static __always_inline u32 xchg_tail(str }return old; } +#endif /* _Q_PENDING_BITS == 8 */ /** * queue_spin_lock_slowpath - acquire the queue spinlock @@ -173,8 +259,13 @@ void queue_spin_lock_slowpath(struct qsp* we're pending, wait for the owner to go away.
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 268
Quoted 10 lines
kernel/locking/qspinlock.c +6
-1* * *,1,1 -> *,1,0 +* +* this wait loop must be a load-acquire such that we match the +* store-release that clears the locked bit and create lock +* sequentiality; this because not all clear_pending_set_locked() +* implementations imply full barriers. */ -while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +while ((val =smp_load_acquire (&lock->val.counter)) & _Q_LOCKED_MASK)
lock->val.counter? Ugh, all to deal with the 'int' -> 'u32' (or 'u64')
Could you introduce a macro in atomic.h called 'atomic_read_raw' which would do the this? Like this:
include/linux/atomic.h +2 -0
@@ -160,6 +160,8 @@ static inline void atomic_or(int i, atomic_t *v) }#endif /* #ifndef CONFIG_ARCH_HAS_ATOMIC_OR */ +#define atomic_read_raw (v) (v.counter) +#include <asm-generic/atomic-long.h> #ifdef CONFIG_GENERIC_ATOMIC64 #include <asm-generic/atomic64.h>
kernel/locking/qspinlock.c +1 -1
@@ -265,7 +265,7 @@ void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val)* sequentiality; this because not all clear_pending_set_locked() * implementations imply full barriers. */ -while ((val =smp_load_acquire (&lock->val.counter)) & _Q_LOCKED_MASK) +while ((val =smp_load_acquire (atomic_read_raw (&lock->val))) & _Q_LOCKED_MASK)arch_mutex_cpu_relax ();/*
?
Quoted 22 lines
kernel/locking/qspinlock.c +1
-9cpu_relax ();/* @@ -182,15 +273,7 @@ void queue_spin_lock_slowpath(struct qsp* * *,1,0 -> *,0,1 */ -for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);return ;/*
Peter Zijlstra
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
Quoted 10 lines · Konrad Rzeszutek Wilk
Quoted 9 lines · Peter Zijlstra
From: Peter Zijlstra <[email protected]>
When we allow for a max
NR_CPUS< 2^14 we can optimize the pending wait-acquire and thexchg_tail()operations.By growing the pending bit to a byte, we reduce the tail to 16bit. This means we can use xchg16 for the tail part and do away with all the repeated
compxchg()operations.
Quoted 8 lines
Quoted 4 lines
This in turn allows us to unconditionally acquire; the locked state as observed by the wait loops cannot change. And because both locked and pending are now a full byte we can use simple stores for the state transition, obviating one atomic operation entirely.
I have to ask - how much more performance do you get from this?
Is this extra atomic operation hurting that much?
Its not extra, its a cmpxchg loop vs an unconditional xchg.
And yes, its somewhat tedious to show, but on 4 socket systems you can really see it make a difference. I'll try and run some numbers, I need to reinstall the box.
(there were numbers in the previous threads, but you're right, I should've put some in the Changelog).
Quoted 8 lines
Quoted 8 lines
/**
*queue_spin_lock_slowpath- acquire the queue spinlock(quoted diff) +2
-0@@ -173,8 +259,13 @@ void queue_spin_lock_slowpath(struct qsp * we're pending, wait for the owner to go away. * * *,1,1 -> *,1,0 + * + * this wait loop must be a load-acquire such that we match the
Quoted 8 lines
Review void queue_spin_lock_slowpath(struct qsp · line 268
Quoted 6 lines
(quoted diff) +4
-1+ * store-release that clears the locked bit and create lock + * sequentiality; this because not all clear_pending_set_locked() + * implementations imply full barriers. */ - while ((val = atomic_read(&lock->val)) & _Q_LOCKED_MASK) + while ((val = smp_load_acquire(&lock->val.counter)) & _Q_LOCKED_MASK)
lock->val.counter? Ugh, all to deal with the 'int' -> 'u32' (or 'u64')
No, to do atomic_t -> int.
Quoted 2 lines
Could you introduce a macro in
atomic.hcalled 'atomic_read_raw' which would do the this? Like this:
That would be worse I think. It looks like a function returning an rvalue whereas we really want an lvalue.
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Peter Zijlstra
maintainerRe: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
In reply to Konrad Rzeszutek WilkQuoted 10 lines · Konrad Rzeszutek Wilk
Quoted 9 lines · Peter Zijlstra
From: Peter Zijlstra <[email protected]>
When we allow for a max
NR_CPUS< 2^14 we can optimize the pending wait-acquire and thexchg_tail()operations.By growing the pending bit to a byte, we reduce the tail to 16bit. This means we can use xchg16 for the tail part and do away with all the repeated
compxchg()operations.
Quoted 8 lines
Quoted 4 lines
This in turn allows us to unconditionally acquire; the locked state as observed by the wait loops cannot change. And because both locked and pending are now a full byte we can use simple stores for the state transition, obviating one atomic operation entirely.
I have to ask - how much more performance do you get from this?
Is this extra atomic operation hurting that much?
Its not extra, its a cmpxchg loop vs an unconditional xchg.
And yes, its somewhat tedious to show, but on 4 socket systems you can really see it make a difference. I'll try and run some numbers, I need to reinstall the box.
(there were numbers in the previous threads, but you're right, I should've put some in the Changelog).
Quoted 8 lines
Quoted 8 lines
/**
*queue_spin_lock_slowpath- acquire the queue spinlock(quoted diff) +2
-0@@ -173,8 +259,13 @@ void queue_spin_lock_slowpath(struct qsp * we're pending, wait for the owner to go away. * * *,1,1 -> *,1,0 + * + * this wait loop must be a load-acquire such that we match the
Quoted 8 lines
Review void queue_spin_lock_slowpath(struct qsp · line 268
Quoted 6 lines
(quoted diff) +4
-1+ * store-release that clears the locked bit and create lock + * sequentiality; this because not all clear_pending_set_locked() + * implementations imply full barriers. */ - while ((val = atomic_read(&lock->val)) & _Q_LOCKED_MASK) + while ((val = smp_load_acquire(&lock->val.counter)) & _Q_LOCKED_MASK)
lock->val.counter? Ugh, all to deal with the 'int' -> 'u32' (or 'u64')
No, to do atomic_t -> int.
Quoted 2 lines
Could you introduce a macro in
atomic.hcalled 'atomic_read_raw' which would do the this? Like this:
That would be worse I think. It looks like a function returning an rvalue whereas we really want an lvalue.
_______________________________________________
Xen-devel mailing list
[email protected]
http://lists.xen.org/xen-devel
Re: [PATCH 05/11] qspinlock: Optimize for smaller NR_CPUS
In reply to Peter ZijlstraQuoted 5 lines · Peter Zijlstra
From: Peter Zijlstra <[email protected]>
When we allow for a max
NR_CPUS< 2^14 we can optimize the pending wait-acquire and thexchg_tail()operations.
Quoted 8 lines
By growing the pending bit to a byte, we reduce the tail to 16bit. This means we can use xchg16 for the tail part and do away with all the repeated
compxchg()operations.This in turn allows us to unconditionally acquire; the locked state as observed by the wait loops cannot change. And because both locked and pending are now a full byte we can use simple stores for the state transition, obviating one atomic operation entirely.
I have to ask - how much more performance do you get from this?
Is this extra atomic operation hurting that much?
Quoted 102 lines
All this is horribly broken on Alpha pre EV56 (and any other arch that cannot do single-copy atomic byte stores).
Signed-off-by Peter Zijlstra <[email protected]>--- include/asm-generic/qspinlock_types.h | 13 ++++ kernel/locking/qspinlock.c | 103 ++++++++++++++++++++++++++++++---- 2 files changed, 106 insertions(+), 10 deletions(-)include/asm-generic/qspinlock_types.h +13
-0@@ -38,6 +38,14 @@ typedef struct qspinlock {/* * Bitfields in the atomic value: * +* When NR_CPUS < 16K +* 0- 7: locked byte +* 8: pending +* 9-15: not used +* 16-17: tail index +* 18-31: tail cpu (+1) +* +* When NR_CPUS >= 16K * 0- 7: locked byte * 8: pending * 9-10: tail index @@ -50,7 +58,11 @@ typedef struct qspinlock {#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED)#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#if CONFIG_NR_CPUS < (1U <<14 ) +#define _Q_PENDING_BITS 8 +#else #define _Q_PENDING_BITS 1 +#endif #define _Q_PENDING_MASK _Q_SET_MASK(PENDING)#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)@@ -61,6 +73,7 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_OFFSET _Q_TAIL_IDX_OFFSET#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK)#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)kernel/locking/qspinlock.c +35
-0@@ -22,6 +22,7 @@#include <linux/percpu.h> #include <linux/hardirq.h> #include <linux/mutex.h> +#include <asm/byteorder.h> #include <asm/qspinlock.h> /* @@ -48,6 +49,9 @@* We can further change the first spinner to spin on a bit in the lock word * instead of its node; whereby avoiding the need to carry a node from lock to * unlock, and preserving API. +* +* N.B. The current implementation only supports architectures that allow +* atomic operations on smaller 8-bit and 16-bit data types. */ #include "mcs_spinlock.h" @@ -85,6 +89,87 @@ static inline struct mcs_spinlock *decod#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK) +/* +* By using the whole 2nd least significant byte for the pending bit, we +* can allow better optimization of the lock acquisition for the pending +* bit holder. +*/ +#if _Q_PENDING_BITS ==8 + +struct __qspinlock { +union { +atomic_t val; +struct { +#ifdef __LITTLE_ENDIAN +u16 locked_pending; +u16 tail; +#else +u16 tail; +u16 locked_pending; +#endif + }; + }; +}; + +/** +* clear_pending_set_locked - take ownership and clear the pending bit. +* @lock: Pointer to queue spinlock structure +* @val : Current value of the queue spinlock 32-bit word +* +* *,1,0 -> *,0,1 +* +* Lock stealing is not allowed if this function is used. +*/
kernel/locking/qspinlock.c static inline struct mcs_spinlock *decod · line 132
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+static __always_inline void +clear_pending_set_locked (struct qspinlock *lock,u32 val) +{ +struct __qspinlock *l = (void *)lock; + +ACCESS_ONCE (l->locked_pending) = _Q_LOCKED_VAL; +} + +/* +* xchg_tail - Put in the new queue tail code word & retrieve previous one
Missing full stop.
Quoted 53 lines
kernel/locking/qspinlock.c +41
-0+* @lock : Pointer to queue spinlock structure +* @tail : The new queue tail code word +* Return: The previous queue tail code word +* +* xchg(lock, tail) +* +* p,*,* -> n,*,* ; prev = xchg(lock, node) +*/ +static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail) +{ +struct __qspinlock *l = (void *)lock; + +return (u32 )xchg (&l->tail, tail >> _Q_TAIL_OFFSET) << _Q_TAIL_OFFSET; +} + +#else /* _Q_PENDING_BITS == 8 */ + +/** +* clear_pending_set_locked - take ownership and clear the pending bit. +* @lock: Pointer to queue spinlock structure +* @val : Current value of the queue spinlock 32-bit word +* +* *,1,0 -> *,0,1 +*/ +static __always_inline void +clear_pending_set_locked (struct qspinlock *lock,u32 val) +{ +u32 new, old; + +for (;;) { + new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; + + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + + val = old; + } +} +/** * xchg_tail - Put in the new queue tail code word & retrieve previous one * @lock : Pointer to queue spinlock structure @@ -109,6 +194,7 @@ static __always_inline u32 xchg_tail(str }return old; } +#endif /* _Q_PENDING_BITS == 8 */ /** * queue_spin_lock_slowpath - acquire the queue spinlock @@ -173,8 +259,13 @@ void queue_spin_lock_slowpath(struct qsp* we're pending, wait for the owner to go away.
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 268
Quoted 10 lines
kernel/locking/qspinlock.c +6
-1* * *,1,1 -> *,1,0 +* +* this wait loop must be a load-acquire such that we match the +* store-release that clears the locked bit and create lock +* sequentiality; this because not all clear_pending_set_locked() +* implementations imply full barriers. */ -while ((val =atomic_read (&lock->val)) & _Q_LOCKED_MASK) +while ((val =smp_load_acquire (&lock->val.counter)) & _Q_LOCKED_MASK)
lock->val.counter? Ugh, all to deal with the 'int' -> 'u32' (or 'u64')
Could you introduce a macro in atomic.h called 'atomic_read_raw' which would do the this? Like this:
include/linux/atomic.h +2 -0
@@ -160,6 +160,8 @@ static inline void atomic_or(int i, atomic_t *v) }#endif /* #ifndef CONFIG_ARCH_HAS_ATOMIC_OR */ +#define atomic_read_raw (v) (v.counter) +#include <asm-generic/atomic-long.h> #ifdef CONFIG_GENERIC_ATOMIC64 #include <asm-generic/atomic64.h>
kernel/locking/qspinlock.c +1 -1
@@ -265,7 +265,7 @@ void queue_spin_lock_slowpath(struct qspinlock *lock, u32 val)* sequentiality; this because not all clear_pending_set_locked() * implementations imply full barriers. */ -while ((val =smp_load_acquire (&lock->val.counter)) & _Q_LOCKED_MASK) +while ((val =smp_load_acquire (atomic_read_raw (&lock->val))) & _Q_LOCKED_MASK)arch_mutex_cpu_relax ();/*
?
Quoted 22 lines
kernel/locking/qspinlock.c +1
-9cpu_relax ();/* @@ -182,15 +273,7 @@ void queue_spin_lock_slowpath(struct qsp* * *,1,0 -> *,0,1 */ -for (;;) { - new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } +clear_pending_set_locked (lock, val);return ;/*
PATCH 5/11 qspinlock: Optimize for smaller NR_CPUS
In reply to Peter ZijlstraFrom: Peter Zijlstra <[email protected]>
When we allow for a max NR_CPUS < 2^14 we can optimize the pending wait-acquire and the xchg_tail() operations.
By growing the pending bit to a byte, we reduce the tail to 16bit. This means we can use xchg16 for the tail part and do away with all the repeated compxchg() operations.
This in turn allows us to unconditionally acquire; the locked state as observed by the wait loops cannot change. And because both locked and pending are now a full byte we can use simple stores for the state transition, obviating one atomic operation entirely.
All this is horribly broken on Alpha pre EV56 (and any other arch that cannot do single-copy atomic byte stores).
--- include/asm-generic/qspinlock_types.h | 13 ++++ kernel/locking/qspinlock.c | 103 ++++++++++++++++++++++++++++++---- 2 files changed, 106 insertions(+), 10 deletions(-)
include/asm-generic/qspinlock_types.h +13 -0
@@ -38,6 +38,14 @@ typedef struct qspinlock {/* * Bitfields in the atomic value: * +* When NR_CPUS < 16K +* 0- 7: locked byte +* 8: pending +* 9-15: not used +* 16-17: tail index +* 18-31: tail cpu (+1) +* +* When NR_CPUS >= 16K * 0- 7: locked byte * 8: pending * 9-10: tail index
@@ -50,7 +58,11 @@ typedef struct qspinlock {#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED)#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS) +#if CONFIG_NR_CPUS < (1U <<14 ) +#define _Q_PENDING_BITS 8 +#else #define _Q_PENDING_BITS 1 +#endif #define _Q_PENDING_MASK _Q_SET_MASK(PENDING)#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)
@@ -61,6 +73,7 @@ typedef struct qspinlock {#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU) +#define _Q_TAIL_OFFSET _Q_TAIL_IDX_OFFSET#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK)#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)
kernel/locking/qspinlock.c +93 -10
140 lines not on this page · raw message
PATCH 6/11 qspinlock: Optimize pending bit
In reply to Peter ZijlstraXXX: merge into the pending bit patch..
It is possible so observe the pending bit without the locked bit when the last owner has just released but the pending owner has not yet taken ownership.
In this case we would normally queue -- because the pending bit is already taken. However, in this case the pending bit is guaranteed to be released 'soon', therefore wait for it and avoid queueing.
--- kernel/locking/qspinlock.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
kernel/locking/qspinlock.c +10 -0
@@ -226,6 +226,16 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS));/* +* wait for in-progress pending->locked hand-overs +* +* 0,1,0 -> 0,0,1 +*/ +if (val == _Q_PENDING_VAL) { +while ((val =atomic_read (&lock->val)) == _Q_PENDING_VAL) +cpu_relax (); + } + +/* * trylock || pending * * 0,0,0 -> 0,0,1 ; trylock
Paolo Bonzini
maintainerRe: [PATCH 06/11] qspinlock: Optimize pending bit
Il 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 1 line
XXX: merge into the pending bit patch..
Agree, or if not move it right after the pending bit patch, before the NR_CPUS optimization.
Paolo
Quoted 9 lines
It is possible so observe the pending bit without the locked bit when the last owner has just released but the pending owner has not yet taken ownership.
In this case we would normally queue -- because the pending bit is already taken. However, in this case the pending bit is guaranteed to be released 'soon', therefore wait for it and avoid queueing.
Signed-off-by Peter Zijlstra <[email protected]>
Paolo Bonzini
maintainerRe: [PATCH 06/11] qspinlock: Optimize pending bit
In reply to Peter ZijlstraIl 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 1 line
XXX: merge into the pending bit patch..
Agree, or if not move it right after the pending bit patch, before the NR_CPUS optimization.
Paolo
Quoted 9 lines
It is possible so observe the pending bit without the locked bit when the last owner has just released but the pending owner has not yet taken ownership.
In this case we would normally queue -- because the pending bit is already taken. However, in this case the pending bit is guaranteed to be released 'soon', therefore wait for it and avoid queueing.
Signed-off-by Peter Zijlstra <[email protected]>
PATCH 6/11 qspinlock: Optimize pending bit
In reply to Peter ZijlstraXXX: merge into the pending bit patch..
It is possible so observe the pending bit without the locked bit when the last owner has just released but the pending owner has not yet taken ownership.
In this case we would normally queue -- because the pending bit is already taken. However, in this case the pending bit is guaranteed to be released 'soon', therefore wait for it and avoid queueing.
--- kernel/locking/qspinlock.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
kernel/locking/qspinlock.c +10 -0
@@ -226,6 +226,16 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS));/* +* wait for in-progress pending->locked hand-overs +* +* 0,1,0 -> 0,0,1 +*/ +if (val == _Q_PENDING_VAL) { +while ((val =atomic_read (&lock->val)) == _Q_PENDING_VAL) +cpu_relax (); + } + +/* * trylock || pending * * 0,0,0 -> 0,0,1 ; trylock
PATCH 6/11 qspinlock: Optimize pending bit
In reply to Peter ZijlstraXXX: merge into the pending bit patch..
It is possible so observe the pending bit without the locked bit when the last owner has just released but the pending owner has not yet taken ownership.
In this case we would normally queue -- because the pending bit is already taken. However, in this case the pending bit is guaranteed to be released 'soon', therefore wait for it and avoid queueing.
--- kernel/locking/qspinlock.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
kernel/locking/qspinlock.c +10 -0
@@ -226,6 +226,16 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS));/* +* wait for in-progress pending->locked hand-overs +* +* 0,1,0 -> 0,0,1 +*/ +if (val == _Q_PENDING_VAL) { +while ((val =atomic_read (&lock->val)) == _Q_PENDING_VAL) +cpu_relax (); + } + +/* * trylock || pending * * 0,0,0 -> 0,0,1 ; trylock
PATCH 7/11 qspinlock: Use a simple write to grab the lock, if applicable
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
Currently, atomic_cmpxchg() is used to get the lock. However, this is not really necessary if there is more than one task in the queue and the queue head don't need to reset the queue code word. For that case, a simple write to set the lock bit is enough as the queue head will be the only one eligible to get the lock as long as it checks that both the lock and pending bits are not set. The current pending bit waiting code will ensure that the bit will not be set as soon as the queue code word (tail) in the lock is set.
With that change, the are some slight improvement in the performance of the queue spinlock in the 5M loop micro-benchmark run on a 4-socket Westere-EX machine as shown in the tables below.
[Standalone/Embedded - same node]
# of tasks Before patch After patch %Change
---------- ----------- ---------- -------
3 2324/2321 2248/2265 -3%/-2%
4 2890/2896 2819/2831 -2%/-2%
5 3611/3595 3522/3512 -2%/-2%
6 4281/4276 4173/4160 -3%/-3%
7 5018/5001 4875/4861 -3%/-3%
8 5759/5750 5563/5568 -3%/-3%[Standalone/Embedded - different nodes]
# of tasks Before patch After patch %Change
---------- ----------- ---------- -------
3 12242/12237 12087/12093 -1%/-1%
4 10688/10696 10507/10521 -2%/-2%It was also found that this change produced a much bigger performance improvement in the newer IvyBridge-EX chip and was essentially to close the performance gap between the ticket spinlock and queue spinlock.
The disk workload of the AIM7 benchmark was run on a 4-socket Westmere-EX machine with both ext4 and xfs RAM disks at 3000 users on a 3.14 based kernel. The results of the test runs were:
AIM7 XFS Disk Test
kernel JPM Real Time Sys Time Usr Time ----- --- --------- -------- -------- ticketlock 5678233 3.17 96.61 5.81 qspinlock 5750799 3.13 94.83 5.97
AIM7 EXT4 Disk Test
kernel JPM Real Time Sys Time Usr Time ----- --- --------- -------- -------- ticketlock 1114551 16.15 509.72 7.11 qspinlock 2184466 8.24 232.99 6.01
The ext4 filesystem run had a much higher spinlock contention than the xfs filesystem run.
The "ebizzy -m" test was also run with the following results:
kernel records/s Real Time Sys Time Usr Time ----- --------- --------- -------- -------- ticketlock 2075 10.00 216.35 3.49 qspinlock 3023 10.00 198.20 4.80
--- kernel/locking/qspinlock.c | 59 ++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 16 deletions(-)
kernel/locking/qspinlock.c +43 -16
@@ -93,24 +93,33 @@ static inline struct mcs_spinlock *decod* By using the whole 2nd least significant byte for the pending bit, we * can allow better optimization of the lock acquisition for the pending * bit holder. +* +* This internal structure is also used by the set_locked function which +* is not restricted to _Q_PENDING_BITS == 8. */ -#if _Q_PENDING_BITS ==8 -struct __qspinlock {union {atomic_t val; -struct {#ifdef __LITTLE_ENDIAN +u8 locked; +struct {u16 locked_pending;u16 tail; + };#else +struct {u16 tail;u16 locked_pending; -#endif }; +struct { +u8 reserved[3 ]; +u8 locked; + }; +#endif }; }; +#if _Q_PENDING_BITS ==8 /** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queue spinlock structure
@@ -197,6 +206,19 @@ static __always_inline u32 xchg_tail(str#endif /* _Q_PENDING_BITS == 8 */ /** +* set_locked - Set the lock bit and own the lock +* @lock: Pointer to queue spinlock structure +* +* *,*,0 -> *,0,1 +*/ +static __always_inline void set_locked(struct qspinlock *lock) +{ +struct __qspinlock *l = (void *)lock; + +ACCESS_ONCE (l->locked) = _Q_LOCKED_VAL; +} + +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word
@@ -328,10 +350,13 @@ void queue_spin_lock_slowpath(struct qsp/* * we're at the head of the waitqueue, wait for the owner & pending to * go away. +* Load-acquired is used here because the set_locked() +* function below may not be a full memory barrier. * * *,x,y -> *,0,0 */ -while ((val =atomic_read (&lock->val)) & _Q_LOCKED_PENDING_MASK) +while ((val =smp_load_acquire (&lock->val.counter)) & + _Q_LOCKED_PENDING_MASK)cpu_relax ();/*
@@ -339,15 +364,19 @@ void queue_spin_lock_slowpath(struct qsp* * n,0,0 -> 0,0,1 : lock, uncontended * *,0,0 -> *,0,1 : lock, contended +* +* If the queue head is the only one in the queue (lock value == tail), +* clear the tail code and grab the lock. Otherwise, we only need +* to grab the lock. */ for (;;) { - new = _Q_LOCKED_VAL; -if (val != tail) - new |= val; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) +if (val != tail) { +set_locked (lock);break ; + } + old =atomic_cmpxchg (&lock->val, val, _Q_LOCKED_VAL); +if (old == val) + goto release;/* No contention */ val = old; }
@@ -355,12 +384,10 @@ void queue_spin_lock_slowpath(struct qsp/* * contended path; wait for next, release. */ -if (new != _Q_LOCKED_VAL) { -while (!(next =ACCESS_ONCE (node->next))) -cpu_relax (); +while (!(next =ACCESS_ONCE (node->next))) +cpu_relax (); -arch_mcs_spin_unlock_contended (&next->locked); - } +arch_mcs_spin_unlock_contended (&next->locked); release:/*
Re: [PATCH 07/11] qspinlock: Use a simple write to grab the lock, if applicable
Quoted 5 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
Currently,
atomic_cmpxchg()is used to get the lock. However, this is not really necessary if there is more than one task in the queue and the queue head don't need to reset the queue code word. For that case,
s/queue code word/tail {number,value}/ ?
Quoted 5 lines
a simple write to set the lock bit is enough as the queue head will be the only one eligible to get the lock as long as it checks that both the lock and pending bits are not set. The current pending bit waiting code will ensure that the bit will not be set as soon as the queue code word (tail) in the lock is set.
Just use the same word as above.
Quoted 98 lines
With that change, the are some slight improvement in the performance of the queue spinlock in the 5M loop micro-benchmark run on a 4-socket Westere-EX machine as shown in the tables below.
[Standalone/Embedded - same node]
# of tasks Before patch After patch %Change ---------- ----------- ---------- ------- 3 2324/2321 2248/2265 -3%/-2% 4 2890/2896 2819/2831 -2%/-2% 5 3611/3595 3522/3512 -2%/-2% 6 4281/4276 4173/4160 -3%/-3% 7 5018/5001 4875/4861 -3%/-3% 8 5759/5750 5563/5568 -3%/-3%[Standalone/Embedded - different nodes]
# of tasks Before patch After patch %Change ---------- ----------- ---------- ------- 3 12242/12237 12087/12093 -1%/-1% 4 10688/10696 10507/10521 -2%/-2%It was also found that this change produced a much bigger performance improvement in the newer IvyBridge-EX chip and was essentially to close the performance gap between the ticket spinlock and queue spinlock.
The disk workload of the AIM7 benchmark was run on a 4-socket Westmere-EX machine with both ext4 and xfs RAM disks at 3000 users on a 3.14 based kernel. The results of the test runs were:
AIM7 XFS Disk Test
kernel JPM Real Time Sys Time Usr Time ----- --- --------- -------- -------- ticketlock 5678233 3.17 96.61 5.81 qspinlock 5750799 3.13 94.83 5.97AIM7 EXT4 Disk Test
kernel JPM Real Time Sys Time Usr Time ----- --- --------- -------- -------- ticketlock 1114551 16.15 509.72 7.11 qspinlock 2184466 8.24 232.99 6.01The ext4 filesystem run had a much higher spinlock contention than the xfs filesystem run.
The "ebizzy -m" test was also run with the following results:
kernel records/s Real Time Sys Time Usr Time ----- --------- --------- -------- -------- ticketlock 2075 10.00 216.35 3.49 qspinlock 3023 10.00 198.20 4.80Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>--- kernel/locking/qspinlock.c | 59 ++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 16 deletions(-)kernel/locking/qspinlock.c +13
-4@@ -93,24 +93,33 @@ static inline struct mcs_spinlock *decod* By using the whole 2nd least significant byte for the pending bit, we * can allow better optimization of the lock acquisition for the pending * bit holder. +* +* This internal structure is also used by the set_locked function which +* is not restricted to _Q_PENDING_BITS == 8. */ -#if _Q_PENDING_BITS ==8 -struct __qspinlock {union {atomic_t val; -struct {#ifdef __LITTLE_ENDIAN +u8 locked; +struct {u16 locked_pending;u16 tail; + };#else +struct {u16 tail;u16 locked_pending; -#endif }; +struct { +u8 reserved[3 ]; +u8 locked; + }; +#endif }; }; +#if _Q_PENDING_BITS ==8 /** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queue spinlock structure
kernel/locking/qspinlock.c static __always_inline u32 xchg_tail(str · line 209
Quoted 4 lines
kernel/locking/qspinlock.c +1
-0@@ -197,6 +206,19 @@ static __always_inline u32 xchg_tail(str#endif /* _Q_PENDING_BITS == 8 */ /** +* set_locked - Set the lock bit and own the lock
Full stop missing.
kernel/locking/qspinlock.c static __always_inline u32 xchg_tail(str · line 210
Quoted 1 line
kernel/locking/qspinlock.c +1
-0+* @lock: Pointer to queue spinlock structure
Ditto.
Quoted 73 lines
kernel/locking/qspinlock.c +28
-12+* +* *,*,0 -> *,0,1 +*/ +static __always_inline void set_locked(struct qspinlock *lock) +{ +struct __qspinlock *l = (void *)lock; + +ACCESS_ONCE (l->locked) = _Q_LOCKED_VAL; +} + +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word @@ -328,10 +350,13 @@ void queue_spin_lock_slowpath(struct qsp/* * we're at the head of the waitqueue, wait for the owner & pending to * go away. +* Load-acquired is used here because the set_locked() +* function below may not be a full memory barrier. * * *,x,y -> *,0,0 */ -while ((val =atomic_read (&lock->val)) & _Q_LOCKED_PENDING_MASK) +while ((val =smp_load_acquire (&lock->val.counter)) & + _Q_LOCKED_PENDING_MASK)cpu_relax ();/* @@ -339,15 +364,19 @@ void queue_spin_lock_slowpath(struct qsp* * n,0,0 -> 0,0,1 : lock, uncontended * *,0,0 -> *,0,1 : lock, contended +* +* If the queue head is the only one in the queue (lock value == tail), +* clear the tail code and grab the lock. Otherwise, we only need +* to grab the lock. */ for (;;) { - new = _Q_LOCKED_VAL; -if (val != tail) - new |= val; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) +if (val != tail) { +set_locked (lock);break ; + } + old =atomic_cmpxchg (&lock->val, val, _Q_LOCKED_VAL); +if (old == val) + goto release;/* No contention */ val = old; }@@ -355,12 +384,10 @@ void queue_spin_lock_slowpath(struct qsp/* * contended path; wait for next, release. */ -if (new != _Q_LOCKED_VAL) { -while (!(next =ACCESS_ONCE (node->next))) -cpu_relax (); +while (!(next =ACCESS_ONCE (node->next))) +cpu_relax (); -arch_mcs_spin_unlock_contended (&next->locked); - } +arch_mcs_spin_unlock_contended (&next->locked); release:/*
Peter Zijlstra
maintainerRe: [PATCH 07/11] qspinlock: Use a simple write to grab the lock, if applicable
Quoted 10 lines · Konrad Rzeszutek Wilk
Quoted 5 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
Currently,
atomic_cmpxchg()is used to get the lock. However, this is not really necessary if there is more than one task in the queue and the queue head don't need to reset the queue code word. For that case,s/queue code word/tail {number,value}/ ?
Quoted 7 lines
Quoted 5 lines
a simple write to set the lock bit is enough as the queue head will be the only one eligible to get the lock as long as it checks that both the lock and pending bits are not set. The current pending bit waiting code will ensure that the bit will not be set as soon as the queue code word (tail) in the lock is set.
Just use the same word as above.
I made that:
Currently, atomic_cmpxchg() is used to get the lock. However, this is not really necessary if there is more than one task in the queue and the queue head doesn't need to reset the queue tail.
For that case, a simple write to set the lock byte is enough as the queue head will be the only one eligible to get the lock as long as it checks that both the lock and pending bits are not set. The current pending bit waiting code will ensure that the bit will not be set as soon as the queue tail is set.
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Peter Zijlstra
maintainerRe: [PATCH 07/11] qspinlock: Use a simple write to grab the lock, if applicable
In reply to Konrad Rzeszutek WilkQuoted 10 lines · Konrad Rzeszutek Wilk
Quoted 5 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
Currently,
atomic_cmpxchg()is used to get the lock. However, this is not really necessary if there is more than one task in the queue and the queue head don't need to reset the queue code word. For that case,s/queue code word/tail {number,value}/ ?
Quoted 7 lines
Quoted 5 lines
a simple write to set the lock bit is enough as the queue head will be the only one eligible to get the lock as long as it checks that both the lock and pending bits are not set. The current pending bit waiting code will ensure that the bit will not be set as soon as the queue code word (tail) in the lock is set.
Just use the same word as above.
I made that:
Currently, atomic_cmpxchg() is used to get the lock. However, this is not really necessary if there is more than one task in the queue and the queue head doesn't need to reset the queue tail.
For that case, a simple write to set the lock byte is enough as the queue head will be the only one eligible to get the lock as long as it checks that both the lock and pending bits are not set. The current pending bit waiting code will ensure that the bit will not be set as soon as the queue tail is set.
_______________________________________________
Xen-devel mailing list
[email protected]
http://lists.xen.org/xen-devel
Re: [PATCH 07/11] qspinlock: Use a simple write to grab the lock, if applicable
In reply to Peter ZijlstraQuoted 5 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
Currently,
atomic_cmpxchg()is used to get the lock. However, this is not really necessary if there is more than one task in the queue and the queue head don't need to reset the queue code word. For that case,
s/queue code word/tail {number,value}/ ?
Quoted 5 lines
a simple write to set the lock bit is enough as the queue head will be the only one eligible to get the lock as long as it checks that both the lock and pending bits are not set. The current pending bit waiting code will ensure that the bit will not be set as soon as the queue code word (tail) in the lock is set.
Just use the same word as above.
Quoted 98 lines
With that change, the are some slight improvement in the performance of the queue spinlock in the 5M loop micro-benchmark run on a 4-socket Westere-EX machine as shown in the tables below.
[Standalone/Embedded - same node]
# of tasks Before patch After patch %Change ---------- ----------- ---------- ------- 3 2324/2321 2248/2265 -3%/-2% 4 2890/2896 2819/2831 -2%/-2% 5 3611/3595 3522/3512 -2%/-2% 6 4281/4276 4173/4160 -3%/-3% 7 5018/5001 4875/4861 -3%/-3% 8 5759/5750 5563/5568 -3%/-3%[Standalone/Embedded - different nodes]
# of tasks Before patch After patch %Change ---------- ----------- ---------- ------- 3 12242/12237 12087/12093 -1%/-1% 4 10688/10696 10507/10521 -2%/-2%It was also found that this change produced a much bigger performance improvement in the newer IvyBridge-EX chip and was essentially to close the performance gap between the ticket spinlock and queue spinlock.
The disk workload of the AIM7 benchmark was run on a 4-socket Westmere-EX machine with both ext4 and xfs RAM disks at 3000 users on a 3.14 based kernel. The results of the test runs were:
AIM7 XFS Disk Test
kernel JPM Real Time Sys Time Usr Time ----- --- --------- -------- -------- ticketlock 5678233 3.17 96.61 5.81 qspinlock 5750799 3.13 94.83 5.97AIM7 EXT4 Disk Test
kernel JPM Real Time Sys Time Usr Time ----- --- --------- -------- -------- ticketlock 1114551 16.15 509.72 7.11 qspinlock 2184466 8.24 232.99 6.01The ext4 filesystem run had a much higher spinlock contention than the xfs filesystem run.
The "ebizzy -m" test was also run with the following results:
kernel records/s Real Time Sys Time Usr Time ----- --------- --------- -------- -------- ticketlock 2075 10.00 216.35 3.49 qspinlock 3023 10.00 198.20 4.80Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>--- kernel/locking/qspinlock.c | 59 ++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 16 deletions(-)kernel/locking/qspinlock.c +13
-4@@ -93,24 +93,33 @@ static inline struct mcs_spinlock *decod* By using the whole 2nd least significant byte for the pending bit, we * can allow better optimization of the lock acquisition for the pending * bit holder. +* +* This internal structure is also used by the set_locked function which +* is not restricted to _Q_PENDING_BITS == 8. */ -#if _Q_PENDING_BITS ==8 -struct __qspinlock {union {atomic_t val; -struct {#ifdef __LITTLE_ENDIAN +u8 locked; +struct {u16 locked_pending;u16 tail; + };#else +struct {u16 tail;u16 locked_pending; -#endif }; +struct { +u8 reserved[3 ]; +u8 locked; + }; +#endif }; }; +#if _Q_PENDING_BITS ==8 /** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queue spinlock structure
kernel/locking/qspinlock.c static __always_inline u32 xchg_tail(str · line 209
Quoted 4 lines
kernel/locking/qspinlock.c +1
-0@@ -197,6 +206,19 @@ static __always_inline u32 xchg_tail(str#endif /* _Q_PENDING_BITS == 8 */ /** +* set_locked - Set the lock bit and own the lock
Full stop missing.
kernel/locking/qspinlock.c static __always_inline u32 xchg_tail(str · line 210
Quoted 1 line
kernel/locking/qspinlock.c +1
-0+* @lock: Pointer to queue spinlock structure
Ditto.
Quoted 73 lines
kernel/locking/qspinlock.c +28
-12+* +* *,*,0 -> *,0,1 +*/ +static __always_inline void set_locked(struct qspinlock *lock) +{ +struct __qspinlock *l = (void *)lock; + +ACCESS_ONCE (l->locked) = _Q_LOCKED_VAL; +} + +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word @@ -328,10 +350,13 @@ void queue_spin_lock_slowpath(struct qsp/* * we're at the head of the waitqueue, wait for the owner & pending to * go away. +* Load-acquired is used here because the set_locked() +* function below may not be a full memory barrier. * * *,x,y -> *,0,0 */ -while ((val =atomic_read (&lock->val)) & _Q_LOCKED_PENDING_MASK) +while ((val =smp_load_acquire (&lock->val.counter)) & + _Q_LOCKED_PENDING_MASK)cpu_relax ();/* @@ -339,15 +364,19 @@ void queue_spin_lock_slowpath(struct qsp* * n,0,0 -> 0,0,1 : lock, uncontended * *,0,0 -> *,0,1 : lock, contended +* +* If the queue head is the only one in the queue (lock value == tail), +* clear the tail code and grab the lock. Otherwise, we only need +* to grab the lock. */ for (;;) { - new = _Q_LOCKED_VAL; -if (val != tail) - new |= val; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) +if (val != tail) { +set_locked (lock);break ; + } + old =atomic_cmpxchg (&lock->val, val, _Q_LOCKED_VAL); +if (old == val) + goto release;/* No contention */ val = old; }@@ -355,12 +384,10 @@ void queue_spin_lock_slowpath(struct qsp/* * contended path; wait for next, release. */ -if (new != _Q_LOCKED_VAL) { -while (!(next =ACCESS_ONCE (node->next))) -cpu_relax (); +while (!(next =ACCESS_ONCE (node->next))) +cpu_relax (); -arch_mcs_spin_unlock_contended (&next->locked); - } +arch_mcs_spin_unlock_contended (&next->locked); release:/*
PATCH 7/11 qspinlock: Use a simple write to grab the lock, if applicable
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
Currently, atomic_cmpxchg() is used to get the lock. However, this is not really necessary if there is more than one task in the queue and the queue head don't need to reset the queue code word. For that case, a simple write to set the lock bit is enough as the queue head will be the only one eligible to get the lock as long as it checks that both the lock and pending bits are not set. The current pending bit waiting code will ensure that the bit will not be set as soon as the queue code word (tail) in the lock is set.
With that change, the are some slight improvement in the performance of the queue spinlock in the 5M loop micro-benchmark run on a 4-socket Westere-EX machine as shown in the tables below.
[Standalone/Embedded - same node]
# of tasks Before patch After patch %Change
---------- ----------- ---------- -------
3 2324/2321 2248/2265 -3%/-2%
4 2890/2896 2819/2831 -2%/-2%
5 3611/3595 3522/3512 -2%/-2%
6 4281/4276 4173/4160 -3%/-3%
7 5018/5001 4875/4861 -3%/-3%
8 5759/5750 5563/5568 -3%/-3%[Standalone/Embedded - different nodes]
# of tasks Before patch After patch %Change
---------- ----------- ---------- -------
3 12242/12237 12087/12093 -1%/-1%
4 10688/10696 10507/10521 -2%/-2%It was also found that this change produced a much bigger performance improvement in the newer IvyBridge-EX chip and was essentially to close the performance gap between the ticket spinlock and queue spinlock.
The disk workload of the AIM7 benchmark was run on a 4-socket Westmere-EX machine with both ext4 and xfs RAM disks at 3000 users on a 3.14 based kernel. The results of the test runs were:
AIM7 XFS Disk Test
kernel JPM Real Time Sys Time Usr Time ----- --- --------- -------- -------- ticketlock 5678233 3.17 96.61 5.81 qspinlock 5750799 3.13 94.83 5.97
AIM7 EXT4 Disk Test
kernel JPM Real Time Sys Time Usr Time ----- --- --------- -------- -------- ticketlock 1114551 16.15 509.72 7.11 qspinlock 2184466 8.24 232.99 6.01
The ext4 filesystem run had a much higher spinlock contention than the xfs filesystem run.
The "ebizzy -m" test was also run with the following results:
kernel records/s Real Time Sys Time Usr Time ----- --------- --------- -------- -------- ticketlock 2075 10.00 216.35 3.49 qspinlock 3023 10.00 198.20 4.80
--- kernel/locking/qspinlock.c | 59 ++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 16 deletions(-)
kernel/locking/qspinlock.c +43 -16
@@ -93,24 +93,33 @@ static inline struct mcs_spinlock *decod* By using the whole 2nd least significant byte for the pending bit, we * can allow better optimization of the lock acquisition for the pending * bit holder. +* +* This internal structure is also used by the set_locked function which +* is not restricted to _Q_PENDING_BITS == 8. */ -#if _Q_PENDING_BITS ==8 -struct __qspinlock {union {atomic_t val; -struct {#ifdef __LITTLE_ENDIAN +u8 locked; +struct {u16 locked_pending;u16 tail; + };#else +struct {u16 tail;u16 locked_pending; -#endif }; +struct { +u8 reserved[3 ]; +u8 locked; + }; +#endif }; }; +#if _Q_PENDING_BITS ==8 /** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queue spinlock structure
@@ -197,6 +206,19 @@ static __always_inline u32 xchg_tail(str#endif /* _Q_PENDING_BITS == 8 */ /** +* set_locked - Set the lock bit and own the lock +* @lock: Pointer to queue spinlock structure +* +* *,*,0 -> *,0,1 +*/ +static __always_inline void set_locked(struct qspinlock *lock) +{ +struct __qspinlock *l = (void *)lock; + +ACCESS_ONCE (l->locked) = _Q_LOCKED_VAL; +} + +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure * @val: Current value of the queue spinlock 32-bit word
@@ -328,10 +350,13 @@ void queue_spin_lock_slowpath(struct qsp/* * we're at the head of the waitqueue, wait for the owner & pending to * go away. +* Load-acquired is used here because the set_locked() +* function below may not be a full memory barrier. * * *,x,y -> *,0,0 */ -while ((val =atomic_read (&lock->val)) & _Q_LOCKED_PENDING_MASK) +while ((val =smp_load_acquire (&lock->val.counter)) & + _Q_LOCKED_PENDING_MASK)cpu_relax ();/*
@@ -339,15 +364,19 @@ void queue_spin_lock_slowpath(struct qsp* * n,0,0 -> 0,0,1 : lock, uncontended * *,0,0 -> *,0,1 : lock, contended +* +* If the queue head is the only one in the queue (lock value == tail), +* clear the tail code and grab the lock. Otherwise, we only need +* to grab the lock. */ for (;;) { - new = _Q_LOCKED_VAL; -if (val != tail) - new |= val; - - old =atomic_cmpxchg (&lock->val, val, new); -if (old == val) +if (val != tail) { +set_locked (lock);break ; + } + old =atomic_cmpxchg (&lock->val, val, _Q_LOCKED_VAL); +if (old == val) + goto release;/* No contention */ val = old; }
@@ -355,12 +384,10 @@ void queue_spin_lock_slowpath(struct qsp/* * contended path; wait for next, release. */ -if (new != _Q_LOCKED_VAL) { -while (!(next =ACCESS_ONCE (node->next))) -cpu_relax (); +while (!(next =ACCESS_ONCE (node->next))) +cpu_relax (); -arch_mcs_spin_unlock_contended (&next->locked); - } +arch_mcs_spin_unlock_contended (&next->locked); release:/*
PATCH 8/11 qspinlock: Revert to test-and-set on hypervisors
In reply to Peter ZijlstraWhen we detect a hypervisor (!paravirt, see later patches), revert to a simple test-and-set lock to avoid the horrors of queue preemption.
--- arch/x86/include/asm/qspinlock.h | 14 ++++++++++++++ include/asm-generic/qspinlock.h | 7 +++++++ kernel/locking/qspinlock.c | 3 +++ 3 files changed, 24 insertions(+)
arch/x86/include/asm/qspinlock.h +14 -0
@@ -1,6 +1,7 @@#ifndef _ASM_X86_QSPINLOCK_H#define _ASM_X86_QSPINLOCK_H +#include <asm/cpufeature.h> #include <asm-generic/qspinlock_types.h> #if !defined(CONFIG_X86_OOSTORE ) && !defined(CONFIG_X86_PPRO_FENCE )
@@ -20,6 +21,19 @@ static inline void queue_spin_unlock(str#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */ +#define virt_queue_spin_lock virt_queue_spin_lock + +static inline bool virt_queue_spin_lock (struct qspinlock *lock) +{ +if (!static_cpu_has (X86_FEATURE_HYPERVISOR )) +return false; + +while (atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL) !=0 ) +cpu_relax (); + +return true; +} +#include <asm-generic/qspinlock.h> #endif /* _ASM_X86_QSPINLOCK_H */
include/asm-generic/qspinlock.h +7 -0
@@ -98,6 +98,13 @@ static __always_inline void queue_spin_u }#endif +#ifndef virt_queue_spin_lock +static __always_inline bool virt_queue_spin_lock(struct qspinlock *lock) +{ +return false; +} +#endif +/* * Initializier */
kernel/locking/qspinlock.c +3 -0
@@ -247,6 +247,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (virt_queue_spin_lock (lock)) +return ; +/* * wait for in-progress pending->locked hand-overs *
Re: [PATCH 08/11] qspinlock: Revert to test-and-set on hypervisors
Quoted 59 lines · Peter Zijlstra
When we detect a hypervisor (!paravirt, see later patches), revert to a simple test-and-set lock to avoid the horrors of queue preemption.
Signed-off-by Peter Zijlstra<[email protected]>--- arch/x86/include/asm/qspinlock.h | 14 ++++++++++++++ include/asm-generic/qspinlock.h | 7 +++++++ kernel/locking/qspinlock.c | 3 +++ 3 files changed, 24 insertions(+)arch/x86/include/asm/qspinlock.h +14
-0@@ -1,6 +1,7 @@#ifndef _ASM_X86_QSPINLOCK_H#define _ASM_X86_QSPINLOCK_H +#include <asm/cpufeature.h> #include <asm-generic/qspinlock_types.h> #if !defined(CONFIG_X86_OOSTORE )&& !defined(CONFIG_X86_PPRO_FENCE )@@ -20,6 +21,19 @@ static inline void queue_spin_unlock(str#endif /* !CONFIG_X86_OOSTORE&& !CONFIG_X86_PPRO_FENCE */ +#define virt_queue_spin_lock virt_queue_spin_lock + +static inline bool virt_queue_spin_lock (struct qspinlock *lock) +{ +if (!static_cpu_has (X86_FEATURE_HYPERVISOR )) +return false; + +while (atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL) !=0 ) +cpu_relax (); + +return true; +} +#include <asm-generic/qspinlock.h> #endif /* _ASM_X86_QSPINLOCK_H */ include/asm-generic/qspinlock.h +7
-0@@ -98,6 +98,13 @@ static __always_inline void queue_spin_u }#endif +#ifndef virt_queue_spin_lock +static __always_inline bool virt_queue_spin_lock(struct qspinlock *lock) +{ +return false; +} +#endif +/* * Initializier */
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 255
Quoted 9 lines
kernel/locking/qspinlock.c +3
-0@@ -247,6 +247,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (virt_queue_spin_lock (lock)) +return ; +/* * wait for in-progress pending->locked hand-overs *
I just wonder if it is better to allow the kernel distributors to decide if unfair lock should be the default for virtual guest. Anyway, I have no objection to that myself.
-Longman
Re: [PATCH 08/11] qspinlock: Revert to test-and-set on hypervisors
In reply to Peter ZijlstraQuoted 59 lines · Peter Zijlstra
When we detect a hypervisor (!paravirt, see later patches), revert to a simple test-and-set lock to avoid the horrors of queue preemption.
Signed-off-by Peter Zijlstra<[email protected]>--- arch/x86/include/asm/qspinlock.h | 14 ++++++++++++++ include/asm-generic/qspinlock.h | 7 +++++++ kernel/locking/qspinlock.c | 3 +++ 3 files changed, 24 insertions(+)arch/x86/include/asm/qspinlock.h +14
-0@@ -1,6 +1,7 @@#ifndef _ASM_X86_QSPINLOCK_H#define _ASM_X86_QSPINLOCK_H +#include <asm/cpufeature.h> #include <asm-generic/qspinlock_types.h> #if !defined(CONFIG_X86_OOSTORE )&& !defined(CONFIG_X86_PPRO_FENCE )@@ -20,6 +21,19 @@ static inline void queue_spin_unlock(str#endif /* !CONFIG_X86_OOSTORE&& !CONFIG_X86_PPRO_FENCE */ +#define virt_queue_spin_lock virt_queue_spin_lock + +static inline bool virt_queue_spin_lock (struct qspinlock *lock) +{ +if (!static_cpu_has (X86_FEATURE_HYPERVISOR )) +return false; + +while (atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL) !=0 ) +cpu_relax (); + +return true; +} +#include <asm-generic/qspinlock.h> #endif /* _ASM_X86_QSPINLOCK_H */ include/asm-generic/qspinlock.h +7
-0@@ -98,6 +98,13 @@ static __always_inline void queue_spin_u }#endif +#ifndef virt_queue_spin_lock +static __always_inline bool virt_queue_spin_lock(struct qspinlock *lock) +{ +return false; +} +#endif +/* * Initializier */
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 255
Quoted 9 lines
kernel/locking/qspinlock.c +3
-0@@ -247,6 +247,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (virt_queue_spin_lock (lock)) +return ; +/* * wait for in-progress pending->locked hand-overs *
I just wonder if it is better to allow the kernel distributors to decide if unfair lock should be the default for virtual guest. Anyway, I have no objection to that myself.
-Longman
Re: [PATCH 08/11] qspinlock: Revert to test-and-set on hypervisors
In reply to Peter ZijlstraQuoted 59 lines · Peter Zijlstra
When we detect a hypervisor (!paravirt, see later patches), revert to a simple test-and-set lock to avoid the horrors of queue preemption.
Signed-off-by Peter Zijlstra<[email protected]>--- arch/x86/include/asm/qspinlock.h | 14 ++++++++++++++ include/asm-generic/qspinlock.h | 7 +++++++ kernel/locking/qspinlock.c | 3 +++ 3 files changed, 24 insertions(+)arch/x86/include/asm/qspinlock.h +14
-0@@ -1,6 +1,7 @@#ifndef _ASM_X86_QSPINLOCK_H#define _ASM_X86_QSPINLOCK_H +#include <asm/cpufeature.h> #include <asm-generic/qspinlock_types.h> #if !defined(CONFIG_X86_OOSTORE )&& !defined(CONFIG_X86_PPRO_FENCE )@@ -20,6 +21,19 @@ static inline void queue_spin_unlock(str#endif /* !CONFIG_X86_OOSTORE&& !CONFIG_X86_PPRO_FENCE */ +#define virt_queue_spin_lock virt_queue_spin_lock + +static inline bool virt_queue_spin_lock (struct qspinlock *lock) +{ +if (!static_cpu_has (X86_FEATURE_HYPERVISOR )) +return false; + +while (atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL) !=0 ) +cpu_relax (); + +return true; +} +#include <asm-generic/qspinlock.h> #endif /* _ASM_X86_QSPINLOCK_H */ include/asm-generic/qspinlock.h +7
-0@@ -98,6 +98,13 @@ static __always_inline void queue_spin_u }#endif +#ifndef virt_queue_spin_lock +static __always_inline bool virt_queue_spin_lock(struct qspinlock *lock) +{ +return false; +} +#endif +/* * Initializier */
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 255
Quoted 9 lines
kernel/locking/qspinlock.c +3
-0@@ -247,6 +247,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (virt_queue_spin_lock (lock)) +return ; +/* * wait for in-progress pending->locked hand-overs *
I just wonder if it is better to allow the kernel distributors to decide if unfair lock should be the default for virtual guest. Anyway, I have no objection to that myself.
-Longman
Re: [PATCH 08/11] qspinlock: Revert to test-and-set on hypervisors
In reply to Peter ZijlstraQuoted 1 line · Peter Zijlstra
When we detect a hypervisor (!paravirt, see later patches), revert to
Please spell out the name of the patches.
Quoted 1 line
a simple test-and-set lock to avoid the horrors of queue preemption.
Heheh.
Quoted 68 lines
Signed-off-by Peter Zijlstra <[email protected]>--- arch/x86/include/asm/qspinlock.h | 14 ++++++++++++++ include/asm-generic/qspinlock.h | 7 +++++++ kernel/locking/qspinlock.c | 3 +++ 3 files changed, 24 insertions(+)arch/x86/include/asm/qspinlock.h +14
-0@@ -1,6 +1,7 @@#ifndef _ASM_X86_QSPINLOCK_H#define _ASM_X86_QSPINLOCK_H +#include <asm/cpufeature.h> #include <asm-generic/qspinlock_types.h> #if !defined(CONFIG_X86_OOSTORE ) && !defined(CONFIG_X86_PPRO_FENCE )@@ -20,6 +21,19 @@ static inline void queue_spin_unlock(str#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */ +#define virt_queue_spin_lock virt_queue_spin_lock + +static inline bool virt_queue_spin_lock (struct qspinlock *lock) +{ +if (!static_cpu_has (X86_FEATURE_HYPERVISOR )) +return false; + +while (atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL) !=0 ) +cpu_relax (); + +return true; +} +#include <asm-generic/qspinlock.h> #endif /* _ASM_X86_QSPINLOCK_H */ include/asm-generic/qspinlock.h +7
-0@@ -98,6 +98,13 @@ static __always_inline void queue_spin_u }#endif +#ifndef virt_queue_spin_lock +static __always_inline bool virt_queue_spin_lock(struct qspinlock *lock) +{ +return false; +} +#endif +/* * Initializier */ kernel/locking/qspinlock.c +3
-0@@ -247,6 +247,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (virt_queue_spin_lock (lock)) +return ; +/* * wait for in-progress pending->locked hand-overs *
Re: [PATCH 08/11] qspinlock: Revert to test-and-set on hypervisors
In reply to Peter ZijlstraQuoted 1 line · Peter Zijlstra
When we detect a hypervisor (!paravirt, see later patches), revert to
Please spell out the name of the patches.
Quoted 1 line
a simple test-and-set lock to avoid the horrors of queue preemption.
Heheh.
Quoted 68 lines
Signed-off-by Peter Zijlstra <[email protected]>--- arch/x86/include/asm/qspinlock.h | 14 ++++++++++++++ include/asm-generic/qspinlock.h | 7 +++++++ kernel/locking/qspinlock.c | 3 +++ 3 files changed, 24 insertions(+)arch/x86/include/asm/qspinlock.h +14
-0@@ -1,6 +1,7 @@#ifndef _ASM_X86_QSPINLOCK_H#define _ASM_X86_QSPINLOCK_H +#include <asm/cpufeature.h> #include <asm-generic/qspinlock_types.h> #if !defined(CONFIG_X86_OOSTORE ) && !defined(CONFIG_X86_PPRO_FENCE )@@ -20,6 +21,19 @@ static inline void queue_spin_unlock(str#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */ +#define virt_queue_spin_lock virt_queue_spin_lock + +static inline bool virt_queue_spin_lock (struct qspinlock *lock) +{ +if (!static_cpu_has (X86_FEATURE_HYPERVISOR )) +return false; + +while (atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL) !=0 ) +cpu_relax (); + +return true; +} +#include <asm-generic/qspinlock.h> #endif /* _ASM_X86_QSPINLOCK_H */ include/asm-generic/qspinlock.h +7
-0@@ -98,6 +98,13 @@ static __always_inline void queue_spin_u }#endif +#ifndef virt_queue_spin_lock +static __always_inline bool virt_queue_spin_lock(struct qspinlock *lock) +{ +return false; +} +#endif +/* * Initializier */ kernel/locking/qspinlock.c +3
-0@@ -247,6 +247,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (virt_queue_spin_lock (lock)) +return ; +/* * wait for in-progress pending->locked hand-overs *
PATCH 8/11 qspinlock: Revert to test-and-set on hypervisors
In reply to Peter ZijlstraWhen we detect a hypervisor (!paravirt, see later patches), revert to a simple test-and-set lock to avoid the horrors of queue preemption.
--- arch/x86/include/asm/qspinlock.h | 14 ++++++++++++++ include/asm-generic/qspinlock.h | 7 +++++++ kernel/locking/qspinlock.c | 3 +++ 3 files changed, 24 insertions(+)
arch/x86/include/asm/qspinlock.h +14 -0
@@ -1,6 +1,7 @@#ifndef _ASM_X86_QSPINLOCK_H#define _ASM_X86_QSPINLOCK_H +#include <asm/cpufeature.h> #include <asm-generic/qspinlock_types.h> #if !defined(CONFIG_X86_OOSTORE ) && !defined(CONFIG_X86_PPRO_FENCE )
@@ -20,6 +21,19 @@ static inline void queue_spin_unlock(str#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */ +#define virt_queue_spin_lock virt_queue_spin_lock + +static inline bool virt_queue_spin_lock (struct qspinlock *lock) +{ +if (!static_cpu_has (X86_FEATURE_HYPERVISOR )) +return false; + +while (atomic_cmpxchg (&lock->val,0 , _Q_LOCKED_VAL) !=0 ) +cpu_relax (); + +return true; +} +#include <asm-generic/qspinlock.h> #endif /* _ASM_X86_QSPINLOCK_H */
include/asm-generic/qspinlock.h +7 -0
@@ -98,6 +98,13 @@ static __always_inline void queue_spin_u }#endif +#ifndef virt_queue_spin_lock +static __always_inline bool virt_queue_spin_lock(struct qspinlock *lock) +{ +return false; +} +#endif +/* * Initializier */
kernel/locking/qspinlock.c +3 -0
@@ -247,6 +247,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (virt_queue_spin_lock (lock)) +return ; +/* * wait for in-progress pending->locked hand-overs *
PATCH 9/11 pvqspinlock, x86: Rename paravirt_ticketlocks_enabled
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch renames the paravirt_ticketlocks_enabled static key to a more generic paravirt_spinlocks_enabled name.
--- arch/x86/include/asm/spinlock.h | 4 ++-- arch/x86/kernel/kvm.c | 2 +- arch/x86/kernel/paravirt-spinlocks.c | 4 ++-- arch/x86/xen/spinlock.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-)
arch/x86/include/asm/spinlock.h +2 -2
@@ -39,7 +39,7 @@/* How long a lock should spin before we consider blocking */ #define SPIN_THRESHOLD (1 << 15) -extern struct static_key paravirt_ticketlocks_enabled; +extern struct static_key paravirt_spinlocks_enabled; static __always_inline bool static_key_false(struct static_key *key);#ifdef CONFIG_QUEUE_SPINLOCK
@@ -150,7 +150,7 @@ static inline void __ticket_unlock_slowp static __always_inline void arch_spin_unlock(arch_spinlock_t *lock) {if (TICKET_SLOWPATH_FLAG && -static_key_false (¶virt_ticketlocks_enabled)) { +static_key_false (¶virt_spinlocks_enabled)) {arch_spinlock_t prev; prev = *lock;
arch/x86/kernel/kvm.c +1 -1
@@ -819,7 +819,7 @@ static __init int kvm_spinlock_init_jumpif (!kvm_para_has_feature (KVM_FEATURE_PV_UNHALT ))return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);printk (KERN_INFO "KVM setup paravirtual spinlock\n" );return 0 ;
arch/x86/kernel/paravirt-spinlocks.c +2 -2
@@ -16,5 +16,5 @@ struct pv_lock_ops pv_lock_ops = { };EXPORT_SYMBOL (pv_lock_ops); -struct static_key paravirt_ticketlocks_enabled =STATIC_KEY_INIT_FALSE ; -EXPORT_SYMBOL (paravirt_ticketlocks_enabled); +struct static_key paravirt_spinlocks_enabled =STATIC_KEY_INIT_FALSE ; +EXPORT_SYMBOL (paravirt_spinlocks_enabled);
arch/x86/xen/spinlock.c +1 -1
@@ -293,7 +293,7 @@ static __init int xen_init_spinlocks_jumif (!xen_domain ())return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);return 0 ; }early_initcall (xen_init_spinlocks_jump);
Re: [PATCH 09/11] pvqspinlock, x86: Rename paravirt_ticketlocks_enabled
Quoted 7 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
This patch renames the
paravirt_ticketlocks_enabledstatic key to a more genericparavirt_spinlocks_enabledname.Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>
Quoted 61 lines
--- arch/x86/include/asm/spinlock.h | 4 ++-- arch/x86/kernel/kvm.c | 2 +- arch/x86/kernel/paravirt-spinlocks.c | 4 ++-- arch/x86/xen/spinlock.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-)arch/x86/include/asm/spinlock.h +2
-2@@ -39,7 +39,7 @@/* How long a lock should spin before we consider blocking */ #define SPIN_THRESHOLD (1 << 15) -extern struct static_key paravirt_ticketlocks_enabled; +extern struct static_key paravirt_spinlocks_enabled; static __always_inline bool static_key_false(struct static_key *key);#ifdef CONFIG_QUEUE_SPINLOCK @@ -150,7 +150,7 @@ static inline void __ticket_unlock_slowp static __always_inline void arch_spin_unlock(arch_spinlock_t *lock) {if (TICKET_SLOWPATH_FLAG && -static_key_false (¶virt_ticketlocks_enabled)) { +static_key_false (¶virt_spinlocks_enabled)) {arch_spinlock_t prev; prev = *lock;arch/x86/kernel/kvm.c +1
-1@@ -819,7 +819,7 @@ static __init int kvm_spinlock_init_jumpif (!kvm_para_has_feature (KVM_FEATURE_PV_UNHALT ))return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);printk (KERN_INFO "KVM setup paravirtual spinlock\n" );return 0 ;arch/x86/kernel/paravirt-spinlocks.c +2
-2@@ -16,5 +16,5 @@ struct pv_lock_ops pv_lock_ops = { };EXPORT_SYMBOL (pv_lock_ops); -struct static_key paravirt_ticketlocks_enabled =STATIC_KEY_INIT_FALSE ; -EXPORT_SYMBOL (paravirt_ticketlocks_enabled); +struct static_key paravirt_spinlocks_enabled =STATIC_KEY_INIT_FALSE ; +EXPORT_SYMBOL (paravirt_spinlocks_enabled);arch/x86/xen/spinlock.c +1
-1@@ -293,7 +293,7 @@ static __init int xen_init_spinlocks_jumif (!xen_domain ())return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);return 0 ; }early_initcall (xen_init_spinlocks_jump);
Re: [PATCH 09/11] pvqspinlock, x86: Rename paravirt_ticketlocks_enabled
In reply to Peter ZijlstraQuoted 7 lines · Peter Zijlstra
From: Waiman Long <[email protected]>
This patch renames the
paravirt_ticketlocks_enabledstatic key to a more genericparavirt_spinlocks_enabledname.Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra <[email protected]>
Quoted 61 lines
--- arch/x86/include/asm/spinlock.h | 4 ++-- arch/x86/kernel/kvm.c | 2 +- arch/x86/kernel/paravirt-spinlocks.c | 4 ++-- arch/x86/xen/spinlock.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-)arch/x86/include/asm/spinlock.h +2
-2@@ -39,7 +39,7 @@/* How long a lock should spin before we consider blocking */ #define SPIN_THRESHOLD (1 << 15) -extern struct static_key paravirt_ticketlocks_enabled; +extern struct static_key paravirt_spinlocks_enabled; static __always_inline bool static_key_false(struct static_key *key);#ifdef CONFIG_QUEUE_SPINLOCK @@ -150,7 +150,7 @@ static inline void __ticket_unlock_slowp static __always_inline void arch_spin_unlock(arch_spinlock_t *lock) {if (TICKET_SLOWPATH_FLAG && -static_key_false (¶virt_ticketlocks_enabled)) { +static_key_false (¶virt_spinlocks_enabled)) {arch_spinlock_t prev; prev = *lock;arch/x86/kernel/kvm.c +1
-1@@ -819,7 +819,7 @@ static __init int kvm_spinlock_init_jumpif (!kvm_para_has_feature (KVM_FEATURE_PV_UNHALT ))return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);printk (KERN_INFO "KVM setup paravirtual spinlock\n" );return 0 ;arch/x86/kernel/paravirt-spinlocks.c +2
-2@@ -16,5 +16,5 @@ struct pv_lock_ops pv_lock_ops = { };EXPORT_SYMBOL (pv_lock_ops); -struct static_key paravirt_ticketlocks_enabled =STATIC_KEY_INIT_FALSE ; -EXPORT_SYMBOL (paravirt_ticketlocks_enabled); +struct static_key paravirt_spinlocks_enabled =STATIC_KEY_INIT_FALSE ; +EXPORT_SYMBOL (paravirt_spinlocks_enabled);arch/x86/xen/spinlock.c +1
-1@@ -293,7 +293,7 @@ static __init int xen_init_spinlocks_jumif (!xen_domain ())return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);return 0 ; }early_initcall (xen_init_spinlocks_jump);
PATCH 9/11 pvqspinlock, x86: Rename paravirt_ticketlocks_enabled
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch renames the paravirt_ticketlocks_enabled static key to a more generic paravirt_spinlocks_enabled name.
--- arch/x86/include/asm/spinlock.h | 4 ++-- arch/x86/kernel/kvm.c | 2 +- arch/x86/kernel/paravirt-spinlocks.c | 4 ++-- arch/x86/xen/spinlock.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-)
arch/x86/include/asm/spinlock.h +2 -2
@@ -39,7 +39,7 @@/* How long a lock should spin before we consider blocking */ #define SPIN_THRESHOLD (1 << 15) -extern struct static_key paravirt_ticketlocks_enabled; +extern struct static_key paravirt_spinlocks_enabled; static __always_inline bool static_key_false(struct static_key *key);#ifdef CONFIG_QUEUE_SPINLOCK
@@ -150,7 +150,7 @@ static inline void __ticket_unlock_slowp static __always_inline void arch_spin_unlock(arch_spinlock_t *lock) {if (TICKET_SLOWPATH_FLAG && -static_key_false (¶virt_ticketlocks_enabled)) { +static_key_false (¶virt_spinlocks_enabled)) {arch_spinlock_t prev; prev = *lock;
arch/x86/kernel/kvm.c +1 -1
@@ -819,7 +819,7 @@ static __init int kvm_spinlock_init_jumpif (!kvm_para_has_feature (KVM_FEATURE_PV_UNHALT ))return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);printk (KERN_INFO "KVM setup paravirtual spinlock\n" );return 0 ;
arch/x86/kernel/paravirt-spinlocks.c +2 -2
@@ -16,5 +16,5 @@ struct pv_lock_ops pv_lock_ops = { };EXPORT_SYMBOL (pv_lock_ops); -struct static_key paravirt_ticketlocks_enabled =STATIC_KEY_INIT_FALSE ; -EXPORT_SYMBOL (paravirt_ticketlocks_enabled); +struct static_key paravirt_spinlocks_enabled =STATIC_KEY_INIT_FALSE ; +EXPORT_SYMBOL (paravirt_spinlocks_enabled);
arch/x86/xen/spinlock.c +1 -1
@@ -293,7 +293,7 @@ static __init int xen_init_spinlocks_jumif (!xen_domain ())return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);return 0 ; }early_initcall (xen_init_spinlocks_jump);
PATCH 9/11 pvqspinlock, x86: Rename paravirt_ticketlocks_enabled
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch renames the paravirt_ticketlocks_enabled static key to a more generic paravirt_spinlocks_enabled name.
--- arch/x86/include/asm/spinlock.h | 4 ++-- arch/x86/kernel/kvm.c | 2 +- arch/x86/kernel/paravirt-spinlocks.c | 4 ++-- arch/x86/xen/spinlock.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-)
arch/x86/include/asm/spinlock.h +2 -2
@@ -39,7 +39,7 @@/* How long a lock should spin before we consider blocking */ #define SPIN_THRESHOLD (1 << 15) -extern struct static_key paravirt_ticketlocks_enabled; +extern struct static_key paravirt_spinlocks_enabled; static __always_inline bool static_key_false(struct static_key *key);#ifdef CONFIG_QUEUE_SPINLOCK
@@ -150,7 +150,7 @@ static inline void __ticket_unlock_slowp static __always_inline void arch_spin_unlock(arch_spinlock_t *lock) {if (TICKET_SLOWPATH_FLAG && -static_key_false (¶virt_ticketlocks_enabled)) { +static_key_false (¶virt_spinlocks_enabled)) {arch_spinlock_t prev; prev = *lock;
arch/x86/kernel/kvm.c +1 -1
@@ -819,7 +819,7 @@ static __init int kvm_spinlock_init_jumpif (!kvm_para_has_feature (KVM_FEATURE_PV_UNHALT ))return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);printk (KERN_INFO "KVM setup paravirtual spinlock\n" );return 0 ;
arch/x86/kernel/paravirt-spinlocks.c +2 -2
@@ -16,5 +16,5 @@ struct pv_lock_ops pv_lock_ops = { };EXPORT_SYMBOL (pv_lock_ops); -struct static_key paravirt_ticketlocks_enabled =STATIC_KEY_INIT_FALSE ; -EXPORT_SYMBOL (paravirt_ticketlocks_enabled); +struct static_key paravirt_spinlocks_enabled =STATIC_KEY_INIT_FALSE ; +EXPORT_SYMBOL (paravirt_spinlocks_enabled);
arch/x86/xen/spinlock.c +1 -1
@@ -293,7 +293,7 @@ static __init int xen_init_spinlocks_jumif (!xen_domain ())return 0 ; -static_key_slow_inc (¶virt_ticketlocks_enabled); +static_key_slow_inc (¶virt_spinlocks_enabled);return 0 ; }early_initcall (xen_init_spinlocks_jump);
PATCH 10/11 qspinlock: Paravirt support
In reply to Peter ZijlstraAdd minimal paravirt support.
The code aims for minimal impact on the native case.
On the lock side we add one jump label (asm_goto) and 4 paravirt callee saved calls that default to NOPs. The only effects are the extra NOPs and some pointless MOVs to accomodate the calling convention. No register spills happen because of this (x86_64).
On the unlock side we have one paravirt callee saved call, which defaults to the actual unlock sequence: "movb $0, (%rdi)" and a NOP.
The actual paravirt code comes in 3 parts;
- init_node; this initializes the extra data members required for PV state. PV state data is kept 1 cacheline ahead of the regular data.
- link_and_wait_node/kick_node; these are paired with the regular MCS queueing and are placed resp. before/after the paired MCS ops.
- wait_head/queue_unlock; the interesting part here is finding the head node to kick.
Tracking the head is done in two parts, firstly the pv_wait_head will store its cpu number in whichever node is pointed to by the tail part of the lock word. Secondly, pv_link_and_wait_node() will propagate the existing head from the old to the new tail node.
--- arch/x86/include/asm/paravirt.h | 39 +++++++ arch/x86/include/asm/paravirt_types.h | 15 ++ arch/x86/include/asm/qspinlock.h | 25 ++++ arch/x86/kernel/paravirt-spinlocks.c | 22 ++++ arch/x86/kernel/paravirt_patch_32.c | 7 + arch/x86/kernel/paravirt_patch_64.c | 7 + include/asm-generic/qspinlock.h | 11 ++ kernel/locking/qspinlock.c | 179 +++++++++++++++++++++++++++++++++- 8 files changed, 302 insertions(+), 3 deletions(-)
arch/x86/include/asm/paravirt.h +39 -0
@@ -712,6 +712,44 @@ static inline void __set_fixmap(unsigned #if defined(CONFIG_SMP) && defined(CONFIG_PARAVIRT_SPINLOCKS) +#ifdef CONFIG_QUEUE_SPINLOCK + +static __always_inline void pv_init_node(struct mcs_spinlock *node) +{ + PVOP_VCALLEE1(pv_lock_ops.init_node, node); +} + +static __always_inline void pv_link_and_wait_node(u32 old, struct mcs_spinlock *node) +{ + PVOP_VCALLEE2(pv_lock_ops.link_and_wait_node, old, node); +} + +static __always_inline void pv_kick_node(struct mcs_spinlock *node) +{ + PVOP_VCALLEE1(pv_lock_ops.kick_node, node); +} + +static __always_inline void pv_wait_head(struct qspinlock *lock) +{ + PVOP_VCALLEE1(pv_lock_ops.wait_head, lock); +} + +static __always_inline void pv_queue_unlock(struct qspinlock *lock) +{ + PVOP_VCALLEE1(pv_lock_ops.queue_unlock, lock); +} + +static __always_inline void pv_wait(int *ptr, int val) +{ + PVOP_VCALL2(pv_lock_ops.wait, ptr, val); +} + +static __always_inline void pv_kick(int cpu) +{ + PVOP_VCALL1(pv_lock_ops.kick, cpu); +} + +#else static __always_inline void __ticket_lock_spinning(struct arch_spinlock *lock, __ticket_t ticket) {
@@ -723,6 +761,7 @@ static __always_inline void __ticket_unl {PVOP_VCALL2 (pv_lock_ops.unlock_kick, lock, ticket); } +#endif #endif
arch/x86/include/asm/paravirt_types.h +15 -0
@@ -326,6 +326,9 @@ struct pv_mmu_ops { phys_addr_t phys, pgprot_t flags); }; +struct mcs_spinlock ; +struct qspinlock ; +struct arch_spinlock ;#ifdef CONFIG_SMP #include <asm/spinlock_types.h>
@@ -334,8 +337,20 @@ typedef u16 __ticket_t;#endif struct pv_lock_ops { +#ifdef CONFIG_QUEUE_SPINLOCK +struct paravirt_callee_save init_node; +struct paravirt_callee_save link_and_wait_node; +struct paravirt_callee_save kick_node; + +struct paravirt_callee_save wait_head; +struct paravirt_callee_save queue_unlock; + +void (*wait)(int *ptr,int val); +void (*kick)(int cpu); +#else struct paravirt_callee_save lock_spinning;void (*unlock_kick)(struct arch_spinlock *lock,__ticket_t ticket); +#endif };/* This contains all the paravirt structures: we get a convenient
arch/x86/include/asm/qspinlock.h +23 -2
@@ -3,24 +3,45 @@#include <asm/cpufeature.h> #include <asm-generic/qspinlock_types.h> +#include <asm/paravirt.h> #if !defined(CONFIG_X86_OOSTORE ) && !defined(CONFIG_X86_PPRO_FENCE ) -#define queue_spin_unlock queue_spin_unlock/** * queue_spin_unlock - release a queue spinlock * @lock : Pointer to queue spinlock structure * * An effective smp_store_release() on the least-significant byte. */ -static inline void queue_spin_unlock (struct qspinlock *lock) +static inline void native_queue_unlock (struct qspinlock *lock) {barrier ();ACCESS_ONCE (*(u8 *)lock) =0 ; } +#else + +static inline void native_queue_unlock (struct qspinlock *lock) +{ +atomic_dec (&lock->val); +} +#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */ +#define queue_spin_unlock queue_spin_unlock + +#ifdef CONFIG_PARAVIRT_SPINLOCKS +static inline void queue_spin_unlock (struct qspinlock *lock) +{ +pv_queue_unlock (lock); +} +#else +static inline void queue_spin_unlock (struct qspinlock *lock) +{ +native_queue_unlock (lock); +} +#endif +#define virt_queue_spin_lock virt_queue_spin_lockstatic inline bool virt_queue_spin_lock (struct qspinlock *lock)
arch/x86/kernel/paravirt-spinlocks.c +22 -0
@@ -8,11 +8,33 @@#include <asm/paravirt.h> +#ifdef CONFIG_SMP +#ifdef CONFIG_QUEUE_SPINLOCK +void __native_queue_unlock (struct qspinlock *lock) +{ +native_queue_unlock (lock); +} +PV_CALLEE_SAVE_REGS_THUNK (__native_queue_unlock); +#endif +#endif + struct pv_lock_ops pv_lock_ops = {#ifdef CONFIG_SMP +#ifdef CONFIG_QUEUE_SPINLOCK + .init_node = __PV_IS_CALLEE_SAVE(paravirt_nop), + .link_and_wait_node = __PV_IS_CALLEE_SAVE(paravirt_nop), + .kick_node = __PV_IS_CALLEE_SAVE(paravirt_nop), + + .wait_head = __PV_IS_CALLEE_SAVE(paravirt_nop), + .queue_unlock = PV_CALLEE_SAVE(__native_queue_unlock), + + .wait = paravirt_nop, + .kick = paravirt_nop, +#else .lock_spinning = __PV_IS_CALLEE_SAVE(paravirt_nop), .unlock_kick = paravirt_nop,#endif +#endif };EXPORT_SYMBOL (pv_lock_ops);
arch/x86/kernel/paravirt_patch_32.c +7 -0
@@ -12,6 +12,10 @@ DEF_NATIVE(pv_mmu_ops, read_cr3, "mov %cDEF_NATIVE (pv_cpu_ops, clts,"clts" );DEF_NATIVE (pv_cpu_ops, read_tsc,"rdtsc" ); +#if defined(CONFIG_PARAVIRT_SPINLOCKS ) && defined(CONFIG_QUEUE_SPINLOCK ) +DEF_NATIVE (pv_lock_ops, queue_unlock,"movb $0, (%eax)" ); +#endif +unsigned paravirt_patch_ident_32 (void *insnbuf,unsigned len) {/* arg in %eax, return in %eax */
@@ -47,6 +51,9 @@ unsigned native_patch(u8 type, u16 clobbPATCH_SITE (pv_mmu_ops, write_cr3);PATCH_SITE (pv_cpu_ops, clts);PATCH_SITE (pv_cpu_ops, read_tsc); +#if defined(CONFIG_PARAVIRT_SPINLOCKS ) && defined(CONFIG_QUEUE_SPINLOCK ) +PATCH_SITE (pv_lock_ops, queue_unlock); +#endif patch_site: ret =paravirt_patch_insns (ibuf, len, start, end);
arch/x86/kernel/paravirt_patch_64.c +7 -0
@@ -22,6 +22,10 @@ DEF_NATIVE(pv_cpu_ops, swapgs, "swapgs") DEF_NATIVE(, mov32, "mov %edi, %eax"); DEF_NATIVE(, mov64, "mov %rdi, %rax"); +#if defined(CONFIG_PARAVIRT_SPINLOCKS ) && defined(CONFIG_QUEUE_SPINLOCK ) +DEF_NATIVE (pv_lock_ops, queue_unlock,"movb $0, (%rdi)" ); +#endif +unsigned paravirt_patch_ident_32 (void *insnbuf,unsigned len) { return paravirt_patch_insns(insnbuf, len,
@@ -61,6 +65,9 @@ unsigned native_patch(u8 type, u16 clobbPATCH_SITE (pv_cpu_ops, clts);PATCH_SITE (pv_mmu_ops, flush_tlb_single);PATCH_SITE (pv_cpu_ops, wbinvd); +#if defined(CONFIG_PARAVIRT_SPINLOCKS ) && defined(CONFIG_QUEUE_SPINLOCK ) +PATCH_SITE (pv_lock_ops, queue_unlock); +#endif patch_site: ret =paravirt_patch_insns (ibuf, len, start, end);
include/asm-generic/qspinlock.h +11 -0
@@ -105,6 +105,17 @@ static __always_inline bool virt_queue_s }#endif +#ifdef CONFIG_PARAVIRT_SPINLOCKS +struct mcs_spinlock ; + +extern void __pv_init_node (struct mcs_spinlock *node); +extern void __pv_link_and_wait_node (u32 old,struct mcs_spinlock *node); +extern void __pv_kick_node (struct mcs_spinlock *node); + +extern void __pv_wait_head (struct qspinlock *lock); +extern void __pv_queue_unlock (struct qspinlock *lock); +#endif +/* * Initializier */
kernel/locking/qspinlock.c +178 -1
227 lines not on this page · raw message
Re: [PATCH 10/11] qspinlock: Paravirt support
Quoted 6 lines · Peter Zijlstra
kernel/locking/qspinlock.c +3
-0+#ifdef CONFIG_PARAVIRT_SPINLOCKS + +/*
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 253
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+* Write a comment about how all this works... +*/ + +#define _Q_LOCKED_SLOW (2U<< _Q_LOCKED_OFFSET) + +struct pv_node { +struct mcs_spinlock mcs; +struct mcs_spinlock __offset[3 ]; +int cpu, head; +};
I am wondering why you need the separate cpu and head variables. I thought one will be enough here. The wait code put the cpu number in head, the the kick_cpu code kick the one in cpu which is just the cpu # of the tail.
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 257
Quoted 4 lines
kernel/locking/qspinlock.c +4
-0+ +#define INVALID_HEAD -1 +#define NO_HEAD nr_cpu_ids +
I think it is better to use a constant like -2 for NO_HEAD instead of an external variable.
Quoted 22 lines
kernel/locking/qspinlock.c +22
-0+void __pv_init_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +BUILD_BUG_ON (sizeof (struct pv_node )>5 *sizeof (struct mcs_spinlock )); + + pn->cpu =smp_processor_id (); + pn->head =INVALID_HEAD ; +} + +static inline struct pv_node *pv_decode_tail (u32 tail) +{ +return (struct pv_node *)decode_tail (tail); +} + +void __pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) +{ +struct pv_node *ppn, *pn = (struct pv_node *)node; +unsigned int count; + +if (!(old& _Q_TAIL_MASK)) { + pn->head =NO_HEAD ;
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 289
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+return ; + } + + ppn =pv_decode_tail (old); +ACCESS_ONCE (ppn->mcs.next) = node; + +while (ppn->head ==INVALID_HEAD ) +cpu_relax (); + + pn->head = ppn->head;
A race can happen here as pn->head can be changed to the head cpu by the head waiter while being changed by this function at the same time. It is safer to use cmpxchg to make sure that there is no accidental overwriting of the head CPU number.
Quoted 81 lines
kernel/locking/qspinlock.c +81
-0+ +for (;;) { + count =SPIN_THRESHOLD ; + +do { +if (smp_load_acquire (&node->locked)) +return ; + +cpu_relax (); + }while (--count); + +pv_wait (&node->locked,1 ); + } +} + +void __pv_kick_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +pv_kick (pn->cpu); +} + +void __pv_wait_head (struct qspinlock *lock) +{ +unsigned int count; +struct pv_node *pn; +int val, old, new; + +for (;;) { + count =SPIN_THRESHOLD ; + +do { + val =smp_load_acquire (&lock->val.counter); +if (!(val& _Q_LOCKED_PENDING_MASK)) +return ; + }while (--count); + +do { + pn =pv_decode_tail (atomic_read (&lock->val)); + +while (pn->head ==INVALID_HEAD ) +cpu_relax (); + + pn->head =smp_processor_id (); + + }while (pn !=pv_decode_tail (atomic_read (&lock->val))); + +/* +* Set _Q_LOCKED_SLOW; bail when the lock is free. +*/ + val =atomic_read (&lock->val); +for (;;) { +if (!(val& _Q_LOCKED_PENDING_MASK)) +return ; + new = val | _Q_LOCKED_SLOW; + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + val = old; + } + +/* XXX 16bit would be better */ +pv_wait (&lock->val.counter, new); + } +} + +static void ___pv_kick_head (struct qspinlock *lock) +{ +struct pv_node *pn; + + pn =pv_decode_tail (atomic_read (&lock->val)); + +while (pn->head ==INVALID_HEAD ) +cpu_relax (); + +if (WARN_ON_ONCE (pn->head ==NO_HEAD )) +return ; + +pv_kick (pn->head); +} +
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock); + +if (val& _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +
Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Quoted 48 lines
kernel/locking/qspinlock.c +16
-0+#else + +static inline void pv_init_node (struct mcs_spinlock *node) { } +static inline void pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) { } +static inline void pv_kick_node (struct mcs_spinlock *node) { } + +static inline void pv_wait_head (struct qspinlock *lock) { } + +#endif +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure @@ -247,6 +417,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (pv_enabled ()) + goto queue; +if (virt_queue_spin_lock (lock))return ;@@ -323,6 +496,7 @@ void queue_spin_lock_slowpath(struct qsp node += idx; node->locked =0 ; node->next =NULL ; +pv_init_node (node);/* * We touched a (possibly) cold cacheline in the per-cpu queue node; @@ -343,6 +517,7 @@ void queue_spin_lock_slowpath(struct qsp/* * if there was a previous node; link it and wait. */ +pv_link_and_wait_node (old, node);if (old& _Q_TAIL_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;@@ -358,6 +533,7 @@ void queue_spin_lock_slowpath(struct qsp* * *,x,y -> *,0,0 */ +pv_wait_head (lock);while ((val =smp_load_acquire (&lock->val.counter))& _Q_LOCKED_PENDING_MASK)cpu_relax ();
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 571
Quoted 5 lines
kernel/locking/qspinlock.c +1
-0@@ -391,6 +567,7 @@ void queue_spin_lock_slowpath(struct qspcpu_relax ();arch_mcs_spin_unlock_contended (&next->locked); +pv_kick_node (next);
pv_kick_node is an expensive operation and it can significantly slow down the locking operation if we have to do it for every subsequent task in the queue.
-Longman
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Paolo Bonzini
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
Il 17/06/2014 00:08, Waiman Long ha scritto:
Quoted 5 lines
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock);
Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+ +if (val & _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Could you just use xchg on the locked byte?
Paolo
Re: [PATCH 10/11] qspinlock: Paravirt support
Quoted 15 lines · Paolo Bonzini
Il 17/06/2014 00:08, Waiman Long ha scritto:
Quoted 5 lines
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock);Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+ +if (val & _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Quoted 3 lines
Could you just use xchg on the locked byte?
Paolo
The slowpath flag is just an indication that the queue head cpu might have been suspended. It may not be due to spurious wakeup. Releasing the lock unconditionally may cause the queue to be changed while it is being inspected. It really depending on how the cpu kicking is being handled. My patch delays the unlocking until all the inspections had been done to make sure that we don't waste time doing a cpu kick that is not needed.
-Longman
Re: [PATCH 10/11] qspinlock: Paravirt support
In reply to Paolo BonziniQuoted 15 lines · Paolo Bonzini
Il 17/06/2014 00:08, Waiman Long ha scritto:
Quoted 5 lines
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock);Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+ +if (val & _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Quoted 3 lines
Could you just use xchg on the locked byte?
Paolo
The slowpath flag is just an indication that the queue head cpu might have been suspended. It may not be due to spurious wakeup. Releasing the lock unconditionally may cause the queue to be changed while it is being inspected. It really depending on how the cpu kicking is being handled. My patch delays the unlocking until all the inspections had been done to make sure that we don't waste time doing a cpu kick that is not needed.
-Longman
Peter Zijlstra
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
In reply to Paolo BonziniQuoted 15 lines · Paolo Bonzini
Il 17/06/2014 00:08, Waiman Long ha scritto:
Quoted 5 lines
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock);Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+ +if (val & _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Quoted 1 line
Could you just use xchg on the locked byte?
I'll have to, indeed. This is racy.
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Peter Zijlstra
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
In reply to Paolo BonziniQuoted 15 lines · Paolo Bonzini
Il 17/06/2014 00:08, Waiman Long ha scritto:
Quoted 5 lines
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock);Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+ +if (val & _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Quoted 1 line
Could you just use xchg on the locked byte?
I'll have to, indeed. This is racy.
_______________________________________________
Xen-devel mailing list
[email protected]
http://lists.xen.org/xen-devel
Paolo Bonzini
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
In reply to Waiman LongIl 17/06/2014 00:08, Waiman Long ha scritto:
Quoted 5 lines
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock);
Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+ +if (val & _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Could you just use xchg on the locked byte?
Paolo
Peter Zijlstra
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
In reply to Waiman LongQuoted 10 lines · Waiman Long
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 253
Quoted 5 lines · Peter Zijlstra
kernel/locking/qspinlock.c +5
-0+struct pv_node { +struct mcs_spinlock mcs; +struct mcs_spinlock __offset[3 ]; +int cpu, head; +};I am wondering why you need the separate cpu and head variables. I thought one will be enough here. The wait code put the cpu number in head, the the
kick_cpucode kick the one in cpu which is just the cpu # of the tail.
The @cpu is the current cpu, the @head is the encoded pointer to the queue head, they aren't necessarily the same.
The @head thing is not unlike your next pointer, just backwards.
Quoted 6 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 257
Quoted 3 lines
kernel/locking/qspinlock.c +3
-0+#define INVALID_HEAD -1 +#define NO_HEAD nr_cpu_ids +I think it is better to use a constant like -2 for
NO_HEADinstead of an external variable.
Sure..
Quoted 29 lines
Quoted 29 lines
kernel/locking/qspinlock.c +29
-0+void __pv_init_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +BUILD_BUG_ON (sizeof (struct pv_node )>5 *sizeof (struct mcs_spinlock )); + + pn->cpu =smp_processor_id (); + pn->head =INVALID_HEAD ; +} + +static inline struct pv_node *pv_decode_tail (u32 tail) +{ +return (struct pv_node *)decode_tail (tail); +} + +void __pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) +{ +struct pv_node *ppn, *pn = (struct pv_node *)node; +unsigned int count; + +if (!(old& _Q_TAIL_MASK)) { + pn->head =NO_HEAD ; +return ; + } + + ppn =pv_decode_tail (old); +ACCESS_ONCE (ppn->mcs.next) = node; + +while (ppn->head ==INVALID_HEAD )
Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 289
Quoted 3 lines
kernel/locking/qspinlock.c +3
-0+cpu_relax (); + + pn->head = ppn->head;A race can happen here as
pn->headcan be changed to the head cpu by the head waiter while being changed by this function at the same time. It is safer to use cmpxchg to make sure that there is no accidental overwriting of the head CPU number.
Ok, so I'm not entirely sure I see the race, although its entirely possible, this is far too fragile. But I couldn't get rid of the race with cmpxchg/xchg either.
So the idea is 'simple'; have link_and_wait propagate the 'head' 'pointer' from the old to the new tail, and have wait_head set the 'head' pointer on the current tail every time the top waiter goes to sleep.
There's the obvious race where both happen at the same time and you're not sure which 'head' 'pointer' won. To solve that what I did was:
init:
INVALID_HEAD
link_and_wait:
INVALID_HEAD -> pprev->head , NO_HEAD
wait_head:
!INVALID_HEAD -> new head
This way wait_head must wait for link_and_wait to finish before writing the new head value. Furthermore, if we race such that we obtained the 'old' tail and link_and_wait propagated the 'old' head to the 'new' tail, wait_head will detect this by verifying the tail pointer after writing the new head.
We don't need atomics here afaict, but we have wait loops, which of course suck arse for virt :/
I'm not too fond of this scheme; but I thought I'd try and get rid of that O(n) loop you had for finding the head, we simply cannot assume 'small' number of vcpus.
Quoted 5 lines
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock);
Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+ +if (val& _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Indeed so, xchg it is I suppose :/
Quoted 11 lines
Quoted 11 lines
kernel/locking/qspinlock.c +1
-0@@ -358,6 +533,7 @@ void queue_spin_lock_slowpath(struct qsp* * *,x,y -> *,0,0 */ +pv_wait_head (lock);while ((val =smp_load_acquire (&lock->val.counter))& _Q_LOCKED_PENDING_MASK)cpu_relax ();@@ -391,6 +567,7 @@ void queue_spin_lock_slowpath(struct qspcpu_relax ();
Quoted 6 lines
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 570
Quoted 2 lines
kernel/locking/qspinlock.c +1
-0arch_mcs_spin_unlock_contended (&next->locked); +pv_kick_node (next);
pv_kick_nodeis an expensive operation and it can significantly slow down the locking operation if we have to do it for every subsequent task in the queue.
You might by now have noticed that I don't particularly care too much about (para)virt performance :-) Also, I'm very much trying to get 'simple' things working before making them more complex.
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Peter Zijlstra
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
In reply to Waiman LongQuoted 10 lines · Waiman Long
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 253
Quoted 5 lines · Peter Zijlstra
kernel/locking/qspinlock.c +5
-0+struct pv_node { +struct mcs_spinlock mcs; +struct mcs_spinlock __offset[3 ]; +int cpu, head; +};I am wondering why you need the separate cpu and head variables. I thought one will be enough here. The wait code put the cpu number in head, the the
kick_cpucode kick the one in cpu which is just the cpu # of the tail.
The @cpu is the current cpu, the @head is the encoded pointer to the queue head, they aren't necessarily the same.
The @head thing is not unlike your next pointer, just backwards.
Quoted 6 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 257
Quoted 3 lines
kernel/locking/qspinlock.c +3
-0+#define INVALID_HEAD -1 +#define NO_HEAD nr_cpu_ids +I think it is better to use a constant like -2 for
NO_HEADinstead of an external variable.
Sure..
Quoted 29 lines
Quoted 29 lines
kernel/locking/qspinlock.c +29
-0+void __pv_init_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +BUILD_BUG_ON (sizeof (struct pv_node )>5 *sizeof (struct mcs_spinlock )); + + pn->cpu =smp_processor_id (); + pn->head =INVALID_HEAD ; +} + +static inline struct pv_node *pv_decode_tail (u32 tail) +{ +return (struct pv_node *)decode_tail (tail); +} + +void __pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) +{ +struct pv_node *ppn, *pn = (struct pv_node *)node; +unsigned int count; + +if (!(old& _Q_TAIL_MASK)) { + pn->head =NO_HEAD ; +return ; + } + + ppn =pv_decode_tail (old); +ACCESS_ONCE (ppn->mcs.next) = node; + +while (ppn->head ==INVALID_HEAD )
Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 289
Quoted 3 lines
kernel/locking/qspinlock.c +3
-0+cpu_relax (); + + pn->head = ppn->head;A race can happen here as
pn->headcan be changed to the head cpu by the head waiter while being changed by this function at the same time. It is safer to use cmpxchg to make sure that there is no accidental overwriting of the head CPU number.
Ok, so I'm not entirely sure I see the race, although its entirely possible, this is far too fragile. But I couldn't get rid of the race with cmpxchg/xchg either.
So the idea is 'simple'; have link_and_wait propagate the 'head' 'pointer' from the old to the new tail, and have wait_head set the 'head' pointer on the current tail every time the top waiter goes to sleep.
There's the obvious race where both happen at the same time and you're not sure which 'head' 'pointer' won. To solve that what I did was:
init:
INVALID_HEAD
link_and_wait:
INVALID_HEAD -> pprev->head , NO_HEAD
wait_head:
!INVALID_HEAD -> new head
This way wait_head must wait for link_and_wait to finish before writing the new head value. Furthermore, if we race such that we obtained the 'old' tail and link_and_wait propagated the 'old' head to the 'new' tail, wait_head will detect this by verifying the tail pointer after writing the new head.
We don't need atomics here afaict, but we have wait loops, which of course suck arse for virt :/
I'm not too fond of this scheme; but I thought I'd try and get rid of that O(n) loop you had for finding the head, we simply cannot assume 'small' number of vcpus.
Quoted 5 lines
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock);
Quoted 8 lines
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 5 lines
kernel/locking/qspinlock.c +5
-0+ +if (val& _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Indeed so, xchg it is I suppose :/
Quoted 11 lines
Quoted 11 lines
kernel/locking/qspinlock.c +1
-0@@ -358,6 +533,7 @@ void queue_spin_lock_slowpath(struct qsp* * *,x,y -> *,0,0 */ +pv_wait_head (lock);while ((val =smp_load_acquire (&lock->val.counter))& _Q_LOCKED_PENDING_MASK)cpu_relax ();@@ -391,6 +567,7 @@ void queue_spin_lock_slowpath(struct qspcpu_relax ();
Quoted 6 lines
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 570
Quoted 2 lines
kernel/locking/qspinlock.c +1
-0arch_mcs_spin_unlock_contended (&next->locked); +pv_kick_node (next);
pv_kick_nodeis an expensive operation and it can significantly slow down the locking operation if we have to do it for every subsequent task in the queue.
You might by now have noticed that I don't particularly care too much about (para)virt performance :-) Also, I'm very much trying to get 'simple' things working before making them more complex.
_______________________________________________
Xen-devel mailing list
[email protected]
http://lists.xen.org/xen-devel
Re: [PATCH 10/11] qspinlock: Paravirt support
In reply to Peter ZijlstraQuoted 6 lines · Peter Zijlstra
kernel/locking/qspinlock.c +3
-0+#ifdef CONFIG_PARAVIRT_SPINLOCKS + +/*
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 253
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+* Write a comment about how all this works... +*/ + +#define _Q_LOCKED_SLOW (2U<< _Q_LOCKED_OFFSET) + +struct pv_node { +struct mcs_spinlock mcs; +struct mcs_spinlock __offset[3 ]; +int cpu, head; +};
I am wondering why you need the separate cpu and head variables. I thought one will be enough here. The wait code put the cpu number in head, the the kick_cpu code kick the one in cpu which is just the cpu # of the tail.
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 257
Quoted 4 lines
kernel/locking/qspinlock.c +4
-0+ +#define INVALID_HEAD -1 +#define NO_HEAD nr_cpu_ids +
I think it is better to use a constant like -2 for NO_HEAD instead of an external variable.
Quoted 22 lines
kernel/locking/qspinlock.c +22
-0+void __pv_init_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +BUILD_BUG_ON (sizeof (struct pv_node )>5 *sizeof (struct mcs_spinlock )); + + pn->cpu =smp_processor_id (); + pn->head =INVALID_HEAD ; +} + +static inline struct pv_node *pv_decode_tail (u32 tail) +{ +return (struct pv_node *)decode_tail (tail); +} + +void __pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) +{ +struct pv_node *ppn, *pn = (struct pv_node *)node; +unsigned int count; + +if (!(old& _Q_TAIL_MASK)) { + pn->head =NO_HEAD ;
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 289
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+return ; + } + + ppn =pv_decode_tail (old); +ACCESS_ONCE (ppn->mcs.next) = node; + +while (ppn->head ==INVALID_HEAD ) +cpu_relax (); + + pn->head = ppn->head;
A race can happen here as pn->head can be changed to the head cpu by the head waiter while being changed by this function at the same time. It is safer to use cmpxchg to make sure that there is no accidental overwriting of the head CPU number.
Quoted 81 lines
kernel/locking/qspinlock.c +81
-0+ +for (;;) { + count =SPIN_THRESHOLD ; + +do { +if (smp_load_acquire (&node->locked)) +return ; + +cpu_relax (); + }while (--count); + +pv_wait (&node->locked,1 ); + } +} + +void __pv_kick_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +pv_kick (pn->cpu); +} + +void __pv_wait_head (struct qspinlock *lock) +{ +unsigned int count; +struct pv_node *pn; +int val, old, new; + +for (;;) { + count =SPIN_THRESHOLD ; + +do { + val =smp_load_acquire (&lock->val.counter); +if (!(val& _Q_LOCKED_PENDING_MASK)) +return ; + }while (--count); + +do { + pn =pv_decode_tail (atomic_read (&lock->val)); + +while (pn->head ==INVALID_HEAD ) +cpu_relax (); + + pn->head =smp_processor_id (); + + }while (pn !=pv_decode_tail (atomic_read (&lock->val))); + +/* +* Set _Q_LOCKED_SLOW; bail when the lock is free. +*/ + val =atomic_read (&lock->val); +for (;;) { +if (!(val& _Q_LOCKED_PENDING_MASK)) +return ; + new = val | _Q_LOCKED_SLOW; + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + val = old; + } + +/* XXX 16bit would be better */ +pv_wait (&lock->val.counter, new); + } +} + +static void ___pv_kick_head (struct qspinlock *lock) +{ +struct pv_node *pn; + + pn =pv_decode_tail (atomic_read (&lock->val)); + +while (pn->head ==INVALID_HEAD ) +cpu_relax (); + +if (WARN_ON_ONCE (pn->head ==NO_HEAD )) +return ; + +pv_kick (pn->head); +} +
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock); + +if (val& _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +
Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Quoted 48 lines
kernel/locking/qspinlock.c +16
-0+#else + +static inline void pv_init_node (struct mcs_spinlock *node) { } +static inline void pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) { } +static inline void pv_kick_node (struct mcs_spinlock *node) { } + +static inline void pv_wait_head (struct qspinlock *lock) { } + +#endif +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure @@ -247,6 +417,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (pv_enabled ()) + goto queue; +if (virt_queue_spin_lock (lock))return ;@@ -323,6 +496,7 @@ void queue_spin_lock_slowpath(struct qsp node += idx; node->locked =0 ; node->next =NULL ; +pv_init_node (node);/* * We touched a (possibly) cold cacheline in the per-cpu queue node; @@ -343,6 +517,7 @@ void queue_spin_lock_slowpath(struct qsp/* * if there was a previous node; link it and wait. */ +pv_link_and_wait_node (old, node);if (old& _Q_TAIL_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;@@ -358,6 +533,7 @@ void queue_spin_lock_slowpath(struct qsp* * *,x,y -> *,0,0 */ +pv_wait_head (lock);while ((val =smp_load_acquire (&lock->val.counter))& _Q_LOCKED_PENDING_MASK)cpu_relax ();
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 571
Quoted 5 lines
kernel/locking/qspinlock.c +1
-0@@ -391,6 +567,7 @@ void queue_spin_lock_slowpath(struct qspcpu_relax ();arch_mcs_spin_unlock_contended (&next->locked); +pv_kick_node (next);
pv_kick_node is an expensive operation and it can significantly slow down the locking operation if we have to do it for every subsequent task in the queue.
-Longman
_______________________________________________
Xen-devel mailing list
[email protected]
http://lists.xen.org/xen-devel
Re: [PATCH 10/11] qspinlock: Paravirt support
In reply to Peter ZijlstraI am resending it as my original reply has some HTML code & hence rejected by the mailing lists.
Quoted 6 lines · Peter Zijlstra
kernel/locking/qspinlock.c +3
-0+#ifdef CONFIG_PARAVIRT_SPINLOCKS + +/*
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 253
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+* Write a comment about how all this works... +*/ + +#define _Q_LOCKED_SLOW (2U<< _Q_LOCKED_OFFSET) + +struct pv_node { +struct mcs_spinlock mcs; +struct mcs_spinlock __offset[3 ]; +int cpu, head; +};
I am wondering why you need the separate cpu and head variables. I thought one will be enough here. The wait code put the cpu number in head, the the kick_cpu code kick the one in cpu which is just the cpu # of the tail.
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 257
Quoted 4 lines
kernel/locking/qspinlock.c +4
-0+ +#define INVALID_HEAD -1 +#define NO_HEAD nr_cpu_ids +
I think it is better to use a constant like -2 for NO_HEAD instead of an external variable.
Quoted 22 lines
kernel/locking/qspinlock.c +22
-0+void __pv_init_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +BUILD_BUG_ON (sizeof (struct pv_node )>5 *sizeof (struct mcs_spinlock )); + + pn->cpu =smp_processor_id (); + pn->head =INVALID_HEAD ; +} + +static inline struct pv_node *pv_decode_tail (u32 tail) +{ +return (struct pv_node *)decode_tail (tail); +} + +void __pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) +{ +struct pv_node *ppn, *pn = (struct pv_node *)node; +unsigned int count; + +if (!(old& _Q_TAIL_MASK)) { + pn->head =NO_HEAD ;
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 289
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+return ; + } + + ppn =pv_decode_tail (old); +ACCESS_ONCE (ppn->mcs.next) = node; + +while (ppn->head ==INVALID_HEAD ) +cpu_relax (); + + pn->head = ppn->head;
A race can happen here as pn->head can be changed to the head cpu by the head waiter while being changed by this function at the same time. It is safer to use cmpxchg to make sure that there is no accidental overwriting of the head CPU number.
Quoted 81 lines
kernel/locking/qspinlock.c +81
-0+ +for (;;) { + count =SPIN_THRESHOLD ; + +do { +if (smp_load_acquire (&node->locked)) +return ; + +cpu_relax (); + }while (--count); + +pv_wait (&node->locked,1 ); + } +} + +void __pv_kick_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +pv_kick (pn->cpu); +} + +void __pv_wait_head (struct qspinlock *lock) +{ +unsigned int count; +struct pv_node *pn; +int val, old, new; + +for (;;) { + count =SPIN_THRESHOLD ; + +do { + val =smp_load_acquire (&lock->val.counter); +if (!(val& _Q_LOCKED_PENDING_MASK)) +return ; + }while (--count); + +do { + pn =pv_decode_tail (atomic_read (&lock->val)); + +while (pn->head ==INVALID_HEAD ) +cpu_relax (); + + pn->head =smp_processor_id (); + + }while (pn !=pv_decode_tail (atomic_read (&lock->val))); + +/* +* Set _Q_LOCKED_SLOW; bail when the lock is free. +*/ + val =atomic_read (&lock->val); +for (;;) { +if (!(val& _Q_LOCKED_PENDING_MASK)) +return ; + new = val | _Q_LOCKED_SLOW; + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + val = old; + } + +/* XXX 16bit would be better */ +pv_wait (&lock->val.counter, new); + } +} + +static void ___pv_kick_head (struct qspinlock *lock) +{ +struct pv_node *pn; + + pn =pv_decode_tail (atomic_read (&lock->val)); + +while (pn->head ==INVALID_HEAD ) +cpu_relax (); + +if (WARN_ON_ONCE (pn->head ==NO_HEAD )) +return ; + +pv_kick (pn->head); +} +
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock); + +if (val& _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +
Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Quoted 48 lines
kernel/locking/qspinlock.c +16
-0+#else + +static inline void pv_init_node (struct mcs_spinlock *node) { } +static inline void pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) { } +static inline void pv_kick_node (struct mcs_spinlock *node) { } + +static inline void pv_wait_head (struct qspinlock *lock) { } + +#endif +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure @@ -247,6 +417,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (pv_enabled ()) + goto queue; +if (virt_queue_spin_lock (lock))return ;@@ -323,6 +496,7 @@ void queue_spin_lock_slowpath(struct qsp node += idx; node->locked =0 ; node->next =NULL ; +pv_init_node (node);/* * We touched a (possibly) cold cacheline in the per-cpu queue node; @@ -343,6 +517,7 @@ void queue_spin_lock_slowpath(struct qsp/* * if there was a previous node; link it and wait. */ +pv_link_and_wait_node (old, node);if (old& _Q_TAIL_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;@@ -358,6 +533,7 @@ void queue_spin_lock_slowpath(struct qsp* * *,x,y -> *,0,0 */ +pv_wait_head (lock);while ((val =smp_load_acquire (&lock->val.counter))& _Q_LOCKED_PENDING_MASK)cpu_relax ();
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 571
Quoted 5 lines
kernel/locking/qspinlock.c +1
-0@@ -391,6 +567,7 @@ void queue_spin_lock_slowpath(struct qspcpu_relax ();arch_mcs_spin_unlock_contended (&next->locked); +pv_kick_node (next);
pv_kick_node is an expensive operation and it can significantly slow down the locking operation if we have to do it for every subsequent task in the queue.
-Longman
Re: [PATCH 10/11] qspinlock: Paravirt support
In reply to Peter ZijlstraI am resending it as my original reply has some HTML code & hence rejected by the mailing lists.
Quoted 6 lines · Peter Zijlstra
kernel/locking/qspinlock.c +3
-0+#ifdef CONFIG_PARAVIRT_SPINLOCKS + +/*
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 253
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+* Write a comment about how all this works... +*/ + +#define _Q_LOCKED_SLOW (2U<< _Q_LOCKED_OFFSET) + +struct pv_node { +struct mcs_spinlock mcs; +struct mcs_spinlock __offset[3 ]; +int cpu, head; +};
I am wondering why you need the separate cpu and head variables. I thought one will be enough here. The wait code put the cpu number in head, the the kick_cpu code kick the one in cpu which is just the cpu # of the tail.
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 257
Quoted 4 lines
kernel/locking/qspinlock.c +4
-0+ +#define INVALID_HEAD -1 +#define NO_HEAD nr_cpu_ids +
I think it is better to use a constant like -2 for NO_HEAD instead of an external variable.
Quoted 22 lines
kernel/locking/qspinlock.c +22
-0+void __pv_init_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +BUILD_BUG_ON (sizeof (struct pv_node )>5 *sizeof (struct mcs_spinlock )); + + pn->cpu =smp_processor_id (); + pn->head =INVALID_HEAD ; +} + +static inline struct pv_node *pv_decode_tail (u32 tail) +{ +return (struct pv_node *)decode_tail (tail); +} + +void __pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) +{ +struct pv_node *ppn, *pn = (struct pv_node *)node; +unsigned int count; + +if (!(old& _Q_TAIL_MASK)) { + pn->head =NO_HEAD ;
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 289
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+return ; + } + + ppn =pv_decode_tail (old); +ACCESS_ONCE (ppn->mcs.next) = node; + +while (ppn->head ==INVALID_HEAD ) +cpu_relax (); + + pn->head = ppn->head;
A race can happen here as pn->head can be changed to the head cpu by the head waiter while being changed by this function at the same time. It is safer to use cmpxchg to make sure that there is no accidental overwriting of the head CPU number.
Quoted 81 lines
kernel/locking/qspinlock.c +81
-0+ +for (;;) { + count =SPIN_THRESHOLD ; + +do { +if (smp_load_acquire (&node->locked)) +return ; + +cpu_relax (); + }while (--count); + +pv_wait (&node->locked,1 ); + } +} + +void __pv_kick_node (struct mcs_spinlock *node) +{ +struct pv_node *pn = (struct pv_node *)node; + +pv_kick (pn->cpu); +} + +void __pv_wait_head (struct qspinlock *lock) +{ +unsigned int count; +struct pv_node *pn; +int val, old, new; + +for (;;) { + count =SPIN_THRESHOLD ; + +do { + val =smp_load_acquire (&lock->val.counter); +if (!(val& _Q_LOCKED_PENDING_MASK)) +return ; + }while (--count); + +do { + pn =pv_decode_tail (atomic_read (&lock->val)); + +while (pn->head ==INVALID_HEAD ) +cpu_relax (); + + pn->head =smp_processor_id (); + + }while (pn !=pv_decode_tail (atomic_read (&lock->val))); + +/* +* Set _Q_LOCKED_SLOW; bail when the lock is free. +*/ + val =atomic_read (&lock->val); +for (;;) { +if (!(val& _Q_LOCKED_PENDING_MASK)) +return ; + new = val | _Q_LOCKED_SLOW; + old =atomic_cmpxchg (&lock->val, val, new); +if (old == val) +break ; + val = old; + } + +/* XXX 16bit would be better */ +pv_wait (&lock->val.counter, new); + } +} + +static void ___pv_kick_head (struct qspinlock *lock) +{ +struct pv_node *pn; + + pn =pv_decode_tail (atomic_read (&lock->val)); + +while (pn->head ==INVALID_HEAD ) +cpu_relax (); + +if (WARN_ON_ONCE (pn->head ==NO_HEAD )) +return ; + +pv_kick (pn->head); +} +
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 380
Quoted 10 lines
kernel/locking/qspinlock.c +10
-0+void __pv_queue_unlock (struct qspinlock *lock) +{ +int val =atomic_read (&lock->val); + +native_queue_unlock (lock); + +if (val& _Q_LOCKED_SLOW) +___pv_kick_head (lock); +} +
Again a race can happen here between the reading and writing of the lock value. I can't think of a good way to do that without using cmpxchg.
Quoted 48 lines
kernel/locking/qspinlock.c +16
-0+#else + +static inline void pv_init_node (struct mcs_spinlock *node) { } +static inline void pv_link_and_wait_node (u32 old,struct mcs_spinlock *node) { } +static inline void pv_kick_node (struct mcs_spinlock *node) { } + +static inline void pv_wait_head (struct qspinlock *lock) { } + +#endif +/** * queue_spin_lock_slowpath - acquire the queue spinlock * @lock: Pointer to queue spinlock structure @@ -247,6 +417,9 @@ void queue_spin_lock_slowpath(struct qspBUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); +if (pv_enabled ()) + goto queue; +if (virt_queue_spin_lock (lock))return ;@@ -323,6 +496,7 @@ void queue_spin_lock_slowpath(struct qsp node += idx; node->locked =0 ; node->next =NULL ; +pv_init_node (node);/* * We touched a (possibly) cold cacheline in the per-cpu queue node; @@ -343,6 +517,7 @@ void queue_spin_lock_slowpath(struct qsp/* * if there was a previous node; link it and wait. */ +pv_link_and_wait_node (old, node);if (old& _Q_TAIL_MASK) { prev =decode_tail (old);ACCESS_ONCE (prev->next) = node;@@ -358,6 +533,7 @@ void queue_spin_lock_slowpath(struct qsp* * *,x,y -> *,0,0 */ +pv_wait_head (lock);while ((val =smp_load_acquire (&lock->val.counter))& _Q_LOCKED_PENDING_MASK)cpu_relax ();
kernel/locking/qspinlock.c void queue_spin_lock_slowpath(struct qsp · line 571
Quoted 5 lines
kernel/locking/qspinlock.c +1
-0@@ -391,6 +567,7 @@ void queue_spin_lock_slowpath(struct qspcpu_relax ();arch_mcs_spin_unlock_contended (&next->locked); +pv_kick_node (next);
pv_kick_node is an expensive operation and it can significantly slow down the locking operation if we have to do it for every subsequent task in the queue.
-Longman
Paolo Bonzini
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
In reply to Peter ZijlstraIl 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 16 lines
#if !defined(CONFIG_X86_OOSTORE) && !defined(CONFIG_X86_PPRO_FENCE)
arch/x86/include/asm/qspinlock.h +1
-2-#define queue_spin_unlock queue_spin_unlock/** * queue_spin_unlock - release a queue spinlock * @lock : Pointer to queue spinlock structure * * An effective smp_store_release() on the least-significant byte. */ -static inline void queue_spin_unlock (struct qspinlock *lock) +static inline void native_queue_unlock (struct qspinlock *lock) {barrier ();ACCESS_ONCE (*(u8 *)lock) =0 ;
arch/x86/include/asm/qspinlock.h line 29
Quoted 10 lines
arch/x86/include/asm/qspinlock.h +7
-0} +#else + +static inline void native_queue_unlock (struct qspinlock *lock) +{ +atomic_dec (&lock->val); +} +#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */
Should be (part of) an earlier patch? Also, does it get wrong if (CONFIG_X86_OOSTORE || CONFIG_X86_PPRO_FENCE) && paravirt patches the unlock to a single movb? Of course the paravirt spinlocks could simply depend on !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE.
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 257
Quoted 4 lines
kernel/locking/qspinlock.c +4
-0+ +#define INVALID_HEAD -1 +#define NO_HEAD nr_cpu_ids +
-2, like Waiman said.
Paolo
Paolo Bonzini
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
In reply to Peter ZijlstraIl 15/06/2014 14:47, Peter Zijlstra ha scritto:
Quoted 16 lines
#if !defined(CONFIG_X86_OOSTORE) && !defined(CONFIG_X86_PPRO_FENCE)
arch/x86/include/asm/qspinlock.h +1
-2-#define queue_spin_unlock queue_spin_unlock/** * queue_spin_unlock - release a queue spinlock * @lock : Pointer to queue spinlock structure * * An effective smp_store_release() on the least-significant byte. */ -static inline void queue_spin_unlock (struct qspinlock *lock) +static inline void native_queue_unlock (struct qspinlock *lock) {barrier ();ACCESS_ONCE (*(u8 *)lock) =0 ;
arch/x86/include/asm/qspinlock.h line 29
Quoted 10 lines
arch/x86/include/asm/qspinlock.h +7
-0} +#else + +static inline void native_queue_unlock (struct qspinlock *lock) +{ +atomic_dec (&lock->val); +} +#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */
Should be (part of) an earlier patch? Also, does it get wrong if (CONFIG_X86_OOSTORE || CONFIG_X86_PPRO_FENCE) && paravirt patches the unlock to a single movb? Of course the paravirt spinlocks could simply depend on !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE.
kernel/locking/qspinlock.c static __always_inline void set_locked(s · line 257
Quoted 4 lines
kernel/locking/qspinlock.c +4
-0+ +#define INVALID_HEAD -1 +#define NO_HEAD nr_cpu_ids +
-2, like Waiman said.
Paolo
Re: [PATCH 10/11] qspinlock: Paravirt support
In reply to Peter ZijlstraQuoted 3 lines · Peter Zijlstra
Add minimal paravirt support.
The code aims for minimal impact on the native case.
Woot!
Quoted 17 lines
On the lock side we add one jump label (
asm_goto) and 4 paravirt callee saved calls that default to NOPs. The only effects are the extra NOPs and some pointless MOVs to accomodate the calling convention. No register spills happen because of this (x86_64).On the unlock side we have one paravirt callee saved call, which defaults to the actual unlock sequence: "movb $0, (%rdi)" and a NOP.
The actual paravirt code comes in 3 parts;
-
init_node; this initializes the extra data members required for PV state. PV state data is kept 1 cacheline ahead of the regular data.- link_and_wait_node/kick_node; these are paired with the regular MCS queueing and are placed resp. before/after the paired MCS ops.
Quoted 7 lines
- wait_head/queue_unlock; the interesting part here is finding the head node to kick.
Tracking the head is done in two parts, firstly the
pv_wait_headwill store its cpu number in whichever node is pointed to by the tail part of the lock word. Secondly,pv_link_and_wait_node()will propagate the existing head from the old to the new tail node.
I dug in the code and I have some comments about it, but before I post them I was wondering if you have any plans to run any performance tests against the PV ticketlock with normal and over-committed scenarios?
Looking at this with a pen and paper I see that compared to PV ticketlock for the CPUs that are contending on the queue (so they go to pv_wait_head_and_link, then progress to pv_wait_head), they go sleep twice and get woken up twice. In PV ticketlock the contending CPUs would only go to sleep once and woken up once it was their turn.
That of course is the worst case scenario - where the CPU that has the lock is taking forever to do its job and the host is quite overcommitted.
Thanks!
Peter Zijlstra
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
Quoted 3 lines · Konrad Rzeszutek Wilk
I dug in the code and I have some comments about it, but before I post them I was wondering if you have any plans to run any performance tests against the PV ticketlock with normal and over-committed scenarios?
I can barely boot a guest.. I'm not sure I can make them do anything much at all yet. All this virt crap is totally painful.
Re: [PATCH 10/11] qspinlock: Paravirt support
Quoted 7 lines · Peter Zijlstra
Quoted 3 lines · Konrad Rzeszutek Wilk
I dug in the code and I have some comments about it, but before I post them I was wondering if you have any plans to run any performance tests against the PV ticketlock with normal and over-committed scenarios?
I can barely boot a guest.. I'm not sure I can make them do anything much at all yet. All this virt crap is totally painful.
HA!
The reason I asked about that is from a pen-and-paper view it looks suboptimal in the worst case scenario compared to PV ticketlock.
The 'worst case scenario' is when we over-commit (more CPUs than there are physical CPUs) or have to delay guests (the sum of all virtual CPUs > physical CPUs and all of the guests are compiling kernels).
In those cases the PV ticketlock goes to sleep and gets woken up once the ticket holder has finished. In the PV qspinlock we do wake up the first in queue, but we also wake the next one in queue so it can progress further. And so on.
Perhaps a better mechanism is just ditch the queue part and utilize the byte part and under KVM and Xen just do bytelocking (since we have 8 bits). For the PV halt/waking we can stash in the 'struct mcs' the current lock that each CPU is waiting for. And the unlocker can iterate over all of those and wake them all up. Perhaps make the iteration random. Anyhow, that is how the old PV bytelock under Xen worked (before 3.11) and it had worked pretty well (it didn't do it random thought - always started with 'for_each_online_cpu').
Squashing in the ticketlock concept in qspinlock for PV looks scary.
And as I said - this is all pen-and-paper - so it might be that this 'wake-up-go-sleep-on-the-queue' kick is actually not that bad?
Lastly - thank you for taking a stab at this.
Quoted 1 line
Re: [PATCH 10/11] qspinlock: Paravirt support
In reply to Peter ZijlstraQuoted 7 lines · Peter Zijlstra
Quoted 3 lines · Konrad Rzeszutek Wilk
I dug in the code and I have some comments about it, but before I post them I was wondering if you have any plans to run any performance tests against the PV ticketlock with normal and over-committed scenarios?
I can barely boot a guest.. I'm not sure I can make them do anything much at all yet. All this virt crap is totally painful.
HA!
The reason I asked about that is from a pen-and-paper view it looks suboptimal in the worst case scenario compared to PV ticketlock.
The 'worst case scenario' is when we over-commit (more CPUs than there are physical CPUs) or have to delay guests (the sum of all virtual CPUs > physical CPUs and all of the guests are compiling kernels).
In those cases the PV ticketlock goes to sleep and gets woken up once the ticket holder has finished. In the PV qspinlock we do wake up the first in queue, but we also wake the next one in queue so it can progress further. And so on.
Perhaps a better mechanism is just ditch the queue part and utilize the byte part and under KVM and Xen just do bytelocking (since we have 8 bits). For the PV halt/waking we can stash in the 'struct mcs' the current lock that each CPU is waiting for. And the unlocker can iterate over all of those and wake them all up. Perhaps make the iteration random. Anyhow, that is how the old PV bytelock under Xen worked (before 3.11) and it had worked pretty well (it didn't do it random thought - always started with 'for_each_online_cpu').
Squashing in the ticketlock concept in qspinlock for PV looks scary.
And as I said - this is all pen-and-paper - so it might be that this 'wake-up-go-sleep-on-the-queue' kick is actually not that bad?
Lastly - thank you for taking a stab at this.
Quoted 1 line
Peter Zijlstra
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
In reply to Konrad Rzeszutek WilkQuoted 3 lines · Konrad Rzeszutek Wilk
I dug in the code and I have some comments about it, but before I post them I was wondering if you have any plans to run any performance tests against the PV ticketlock with normal and over-committed scenarios?
I can barely boot a guest.. I'm not sure I can make them do anything much at all yet. All this virt crap is totally painful.
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Peter Zijlstra
maintainerRe: [PATCH 10/11] qspinlock: Paravirt support
In reply to Konrad Rzeszutek WilkQuoted 3 lines · Konrad Rzeszutek Wilk
I dug in the code and I have some comments about it, but before I post them I was wondering if you have any plans to run any performance tests against the PV ticketlock with normal and over-committed scenarios?
I can barely boot a guest.. I'm not sure I can make them do anything much at all yet. All this virt crap is totally painful.
_______________________________________________
Xen-devel mailing list
[email protected]
http://lists.xen.org/xen-devel
Re: [PATCH 10/11] qspinlock: Paravirt support
In reply to Peter ZijlstraQuoted 3 lines · Peter Zijlstra
Add minimal paravirt support.
The code aims for minimal impact on the native case.
Woot!
Quoted 17 lines
On the lock side we add one jump label (
asm_goto) and 4 paravirt callee saved calls that default to NOPs. The only effects are the extra NOPs and some pointless MOVs to accomodate the calling convention. No register spills happen because of this (x86_64).On the unlock side we have one paravirt callee saved call, which defaults to the actual unlock sequence: "movb $0, (%rdi)" and a NOP.
The actual paravirt code comes in 3 parts;
-
init_node; this initializes the extra data members required for PV state. PV state data is kept 1 cacheline ahead of the regular data.- link_and_wait_node/kick_node; these are paired with the regular MCS queueing and are placed resp. before/after the paired MCS ops.
Quoted 7 lines
- wait_head/queue_unlock; the interesting part here is finding the head node to kick.
Tracking the head is done in two parts, firstly the
pv_wait_headwill store its cpu number in whichever node is pointed to by the tail part of the lock word. Secondly,pv_link_and_wait_node()will propagate the existing head from the old to the new tail node.
I dug in the code and I have some comments about it, but before I post them I was wondering if you have any plans to run any performance tests against the PV ticketlock with normal and over-committed scenarios?
Looking at this with a pen and paper I see that compared to PV ticketlock for the CPUs that are contending on the queue (so they go to pv_wait_head_and_link, then progress to pv_wait_head), they go sleep twice and get woken up twice. In PV ticketlock the contending CPUs would only go to sleep once and woken up once it was their turn.
That of course is the worst case scenario - where the CPU that has the lock is taking forever to do its job and the host is quite overcommitted.
Thanks!
PATCH 10/11 qspinlock: Paravirt support
In reply to Peter ZijlstraAdd minimal paravirt support.
The code aims for minimal impact on the native case.
On the lock side we add one jump label (asm_goto) and 4 paravirt callee saved calls that default to NOPs. The only effects are the extra NOPs and some pointless MOVs to accomodate the calling convention. No register spills happen because of this (x86_64).
On the unlock side we have one paravirt callee saved call, which defaults to the actual unlock sequence: "movb $0, (%rdi)" and a NOP.
The actual paravirt code comes in 3 parts;
- init_node; this initializes the extra data members required for PV state. PV state data is kept 1 cacheline ahead of the regular data.
- link_and_wait_node/kick_node; these are paired with the regular MCS queueing and are placed resp. before/after the paired MCS ops.
- wait_head/queue_unlock; the interesting part here is finding the head node to kick.
Tracking the head is done in two parts, firstly the pv_wait_head will store its cpu number in whichever node is pointed to by the tail part of the lock word. Secondly, pv_link_and_wait_node() will propagate the existing head from the old to the new tail node.
--- arch/x86/include/asm/paravirt.h | 39 +++++++ arch/x86/include/asm/paravirt_types.h | 15 ++ arch/x86/include/asm/qspinlock.h | 25 ++++ arch/x86/kernel/paravirt-spinlocks.c | 22 ++++ arch/x86/kernel/paravirt_patch_32.c | 7 + arch/x86/kernel/paravirt_patch_64.c | 7 + include/asm-generic/qspinlock.h | 11 ++ kernel/locking/qspinlock.c | 179 +++++++++++++++++++++++++++++++++- 8 files changed, 302 insertions(+), 3 deletions(-)
arch/x86/include/asm/paravirt.h +39 -0
@@ -712,6 +712,44 @@ static inline void __set_fixmap(unsigned #if defined(CONFIG_SMP) && defined(CONFIG_PARAVIRT_SPINLOCKS) +#ifdef CONFIG_QUEUE_SPINLOCK + +static __always_inline void pv_init_node(struct mcs_spinlock *node) +{ + PVOP_VCALLEE1(pv_lock_ops.init_node, node); +} + +static __always_inline void pv_link_and_wait_node(u32 old, struct mcs_spinlock *node) +{ + PVOP_VCALLEE2(pv_lock_ops.link_and_wait_node, old, node); +} + +static __always_inline void pv_kick_node(struct mcs_spinlock *node) +{ + PVOP_VCALLEE1(pv_lock_ops.kick_node, node); +} + +static __always_inline void pv_wait_head(struct qspinlock *lock) +{ + PVOP_VCALLEE1(pv_lock_ops.wait_head, lock); +} + +static __always_inline void pv_queue_unlock(struct qspinlock *lock) +{ + PVOP_VCALLEE1(pv_lock_ops.queue_unlock, lock); +} + +static __always_inline void pv_wait(int *ptr, int val) +{ + PVOP_VCALL2(pv_lock_ops.wait, ptr, val); +} + +static __always_inline void pv_kick(int cpu) +{ + PVOP_VCALL1(pv_lock_ops.kick, cpu); +} + +#else static __always_inline void __ticket_lock_spinning(struct arch_spinlock *lock, __ticket_t ticket) {
@@ -723,6 +761,7 @@ static __always_inline void __ticket_unl {PVOP_VCALL2 (pv_lock_ops.unlock_kick, lock, ticket); } +#endif #endif
arch/x86/include/asm/paravirt_types.h +15 -0
@@ -326,6 +326,9 @@ struct pv_mmu_ops { phys_addr_t phys, pgprot_t flags); }; +struct mcs_spinlock ; +struct qspinlock ; +struct arch_spinlock ;#ifdef CONFIG_SMP #include <asm/spinlock_types.h>
@@ -334,8 +337,20 @@ typedef u16 __ticket_t;#endif struct pv_lock_ops { +#ifdef CONFIG_QUEUE_SPINLOCK +struct paravirt_callee_save init_node; +struct paravirt_callee_save link_and_wait_node; +struct paravirt_callee_save kick_node; + +struct paravirt_callee_save wait_head; +struct paravirt_callee_save queue_unlock; + +void (*wait)(int *ptr,int val); +void (*kick)(int cpu); +#else struct paravirt_callee_save lock_spinning;void (*unlock_kick)(struct arch_spinlock *lock,__ticket_t ticket); +#endif };/* This contains all the paravirt structures: we get a convenient
arch/x86/include/asm/qspinlock.h +23 -2
@@ -3,24 +3,45 @@#include <asm/cpufeature.h> #include <asm-generic/qspinlock_types.h> +#include <asm/paravirt.h> #if !defined(CONFIG_X86_OOSTORE ) && !defined(CONFIG_X86_PPRO_FENCE ) -#define queue_spin_unlock queue_spin_unlock/** * queue_spin_unlock - release a queue spinlock * @lock : Pointer to queue spinlock structure * * An effective smp_store_release() on the least-significant byte. */ -static inline void queue_spin_unlock (struct qspinlock *lock) +static inline void native_queue_unlock (struct qspinlock *lock) {barrier ();ACCESS_ONCE (*(u8 *)lock) =0 ; } +#else + +static inline void native_queue_unlock (struct qspinlock *lock) +{ +atomic_dec (&lock->val); +} +#endif /* !CONFIG_X86_OOSTORE && !CONFIG_X86_PPRO_FENCE */ +#define queue_spin_unlock queue_spin_unlock + +#ifdef CONFIG_PARAVIRT_SPINLOCKS +static inline void queue_spin_unlock (struct qspinlock *lock) +{ +pv_queue_unlock (lock); +} +#else +static inline void queue_spin_unlock (struct qspinlock *lock) +{ +native_queue_unlock (lock); +} +#endif +#define virt_queue_spin_lock virt_queue_spin_lockstatic inline bool virt_queue_spin_lock (struct qspinlock *lock)
arch/x86/kernel/paravirt-spinlocks.c +22 -0
@@ -8,11 +8,33 @@#include <asm/paravirt.h> +#ifdef CONFIG_SMP +#ifdef CONFIG_QUEUE_SPINLOCK +void __native_queue_unlock (struct qspinlock *lock) +{ +native_queue_unlock (lock); +} +PV_CALLEE_SAVE_REGS_THUNK (__native_queue_unlock); +#endif +#endif + struct pv_lock_ops pv_lock_ops = {#ifdef CONFIG_SMP +#ifdef CONFIG_QUEUE_SPINLOCK + .init_node = __PV_IS_CALLEE_SAVE(paravirt_nop), + .link_and_wait_node = __PV_IS_CALLEE_SAVE(paravirt_nop), + .kick_node = __PV_IS_CALLEE_SAVE(paravirt_nop), + + .wait_head = __PV_IS_CALLEE_SAVE(paravirt_nop), + .queue_unlock = PV_CALLEE_SAVE(__native_queue_unlock), + + .wait = paravirt_nop, + .kick = paravirt_nop, +#else .lock_spinning = __PV_IS_CALLEE_SAVE(paravirt_nop), .unlock_kick = paravirt_nop,#endif +#endif };EXPORT_SYMBOL (pv_lock_ops);
arch/x86/kernel/paravirt_patch_32.c +7 -0
@@ -12,6 +12,10 @@ DEF_NATIVE(pv_mmu_ops, read_cr3, "mov %cDEF_NATIVE (pv_cpu_ops, clts,"clts" );DEF_NATIVE (pv_cpu_ops, read_tsc,"rdtsc" ); +#if defined(CONFIG_PARAVIRT_SPINLOCKS ) && defined(CONFIG_QUEUE_SPINLOCK ) +DEF_NATIVE (pv_lock_ops, queue_unlock,"movb $0, (%eax)" ); +#endif +unsigned paravirt_patch_ident_32 (void *insnbuf,unsigned len) {/* arg in %eax, return in %eax */
@@ -47,6 +51,9 @@ unsigned native_patch(u8 type, u16 clobbPATCH_SITE (pv_mmu_ops, write_cr3);PATCH_SITE (pv_cpu_ops, clts);PATCH_SITE (pv_cpu_ops, read_tsc); +#if defined(CONFIG_PARAVIRT_SPINLOCKS ) && defined(CONFIG_QUEUE_SPINLOCK ) +PATCH_SITE (pv_lock_ops, queue_unlock); +#endif patch_site: ret =paravirt_patch_insns (ibuf, len, start, end);
arch/x86/kernel/paravirt_patch_64.c +7 -0
@@ -22,6 +22,10 @@ DEF_NATIVE(pv_cpu_ops, swapgs, "swapgs") DEF_NATIVE(, mov32, "mov %edi, %eax"); DEF_NATIVE(, mov64, "mov %rdi, %rax"); +#if defined(CONFIG_PARAVIRT_SPINLOCKS ) && defined(CONFIG_QUEUE_SPINLOCK ) +DEF_NATIVE (pv_lock_ops, queue_unlock,"movb $0, (%rdi)" ); +#endif +unsigned paravirt_patch_ident_32 (void *insnbuf,unsigned len) { return paravirt_patch_insns(insnbuf, len,
@@ -61,6 +65,9 @@ unsigned native_patch(u8 type, u16 clobbPATCH_SITE (pv_cpu_ops, clts);PATCH_SITE (pv_mmu_ops, flush_tlb_single);PATCH_SITE (pv_cpu_ops, wbinvd); +#if defined(CONFIG_PARAVIRT_SPINLOCKS ) && defined(CONFIG_QUEUE_SPINLOCK ) +PATCH_SITE (pv_lock_ops, queue_unlock); +#endif patch_site: ret =paravirt_patch_insns (ibuf, len, start, end);
include/asm-generic/qspinlock.h +11 -0
@@ -105,6 +105,17 @@ static __always_inline bool virt_queue_s }#endif +#ifdef CONFIG_PARAVIRT_SPINLOCKS +struct mcs_spinlock ; + +extern void __pv_init_node (struct mcs_spinlock *node); +extern void __pv_link_and_wait_node (u32 old,struct mcs_spinlock *node); +extern void __pv_kick_node (struct mcs_spinlock *node); + +extern void __pv_wait_head (struct qspinlock *lock); +extern void __pv_queue_unlock (struct qspinlock *lock); +#endif +/* * Initializier */
kernel/locking/qspinlock.c +178 -1
227 lines not on this page · raw message
PATCH 11/11 qspinlock, kvm: Add paravirt support
In reply to Peter Zijlstra--- arch/x86/kernel/kvm.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/Kconfig.locks | 2 - 2 files changed, 59 insertions(+), 1 deletion(-)
arch/x86/kernel/kvm.c +58 -0
@@ -569,6 +569,7 @@ static void kvm_kick_cpu(int cpu)kvm_hypercall2 (KVM_HC_KICK_CPU , flags, apicid); } +#ifndef CONFIG_QUEUE_SPINLOCK enum kvm_contention_stat {TAKEN_SLOW , TAKEN_SLOW_PICKUP,
@@ -796,6 +797,51 @@ static void kvm_unlock_kick(struct arch_ } } } +#else /* QUEUE_SPINLOCK */ + +#include <asm-generic/qspinlock.h> + +PV_CALLEE_SAVE_REGS_THUNK (__pv_init_node); +PV_CALLEE_SAVE_REGS_THUNK (__pv_link_and_wait_node); +PV_CALLEE_SAVE_REGS_THUNK (__pv_kick_node); + +PV_CALLEE_SAVE_REGS_THUNK (__pv_wait_head); +PV_CALLEE_SAVE_REGS_THUNK (__pv_queue_unlock); + +void kvm_wait (int *ptr,int val) +{ +unsigned long flags; + +if (in_nmi ()) +return ; + +/* +* Make sure an interrupt handler can't upset things in a +* partially setup state. +*/ +local_irq_save (flags); + +/* +* check again make sure it didn't become free while +* we weren't looking. +*/ +if (ACCESS_ONCE (*ptr) != val) + goto out; + +/* +* halt until it's our turn and kicked. Note that we do safe halt +* for irq enabled case to avoid hang when lock info is overwritten +* in irq spinlock slowpath and no spurious interrupt occur to save us. +*/ +if (arch_irqs_disabled_flags (flags)) +halt (); +else +safe_halt (); + +out: +local_irq_restore (flags); +} +#endif /* QUEUE_SPINLOCK */ /* * Setup pv_lock_ops to exploit KVM_FEATURE_PV_UNHALT if present.
@@ -808,8 +854,20 @@ void __init kvm_spinlock_init(void)if (!kvm_para_has_feature (KVM_FEATURE_PV_UNHALT ))return ; +#ifdef CONFIG_QUEUE_SPINLOCK + pv_lock_ops.init_node =PV_CALLEE_SAVE (__pv_init_node); + pv_lock_ops.link_and_wait_node =PV_CALLEE_SAVE (__pv_link_and_wait_node); + pv_lock_ops.kick_node =PV_CALLEE_SAVE (__pv_kick_node); + + pv_lock_ops.wait_head =PV_CALLEE_SAVE (__pv_wait_head); + pv_lock_ops.queue_unlock =PV_CALLEE_SAVE (__pv_queue_unlock); + + pv_lock_ops.wait = kvm_wait; + pv_lock_ops.kick = kvm_kick_cpu; +#else pv_lock_ops.lock_spinning =PV_CALLEE_SAVE (kvm_lock_spinning); pv_lock_ops.unlock_kick = kvm_unlock_kick; +#endif }static __init int kvm_spinlock_init_jump (void )
kernel/Kconfig.locks +1 -1
@@ -229,7 +229,7 @@ config ARCH_USE_QUEUE_SPINLOCK config QUEUE_SPINLOCK def_bool y if ARCH_USE_QUEUE_SPINLOCK - depends on SMP && !PARAVIRT_SPINLOCKS + depends on SMP && !(PARAVIRT_SPINLOCKS && XEN) config ARCH_USE_QUEUE_RWLOCK bool
Re: [PATCH 11/11] qspinlock, kvm: Add paravirt support
Quoted 2 lines · Peter Zijlstra
Signed-off-by Peter Zijlstra<[email protected]>---
[...]
arch/x86/kernel/kvm.c static void kvm_unlock_kick(struct arch_ · line 821
Quoted 12 lines
arch/x86/kernel/kvm.c +12
-0+ +void kvm_wait (int *ptr,int val) +{ +unsigned long flags; + +if (in_nmi ()) +return ; + +/* +* Make sure an interrupt handler can't upset things in a +* partially setup state. +*/
I am seeing hang with even 2 cpu guest (with patches on top of 3.15-rc6 ). looking further with gdb I see one cpu is stuck with native_halt with slowpath flag(_Q_LOCKED_SLOW) set when it was called.
(gdb) bt
#0 native_halt () at /test/master/arch/x86/include/asm/irqflags.h:55 #1 0xffffffff81033118 in halt (ptr=0xffffffff81eb0e58, val=524291) at /test/master/arch/x86/include/asm/paravirt.h:116 #2 kvm_wait (ptr=0xffffffff81eb0e58, val=524291) at arch/x86/kernel/kvm.c:835 #3 kvm_wait (ptr=0xffffffff81eb0e58, val=524291) at arch/x86/kernel/kvm.c:809 #4 0xffffffff810a2d8e in pv_wait (lock=0xffffffff81eb0e58) at /test/master/arch/x86/include/asm/paravirt.h:744 #5 __pv_wait_head (lock=0xffffffff81eb0e58) at kernel/locking/qspinlock.c:352
Value of lock seem to be 524288 (means already unlocked?) So apart from races Waiman mentioned, are we also in need of smp_mb() here and/or native_queue_unlock()?.
Interestingly I see other cpu stuck at multi_cpu_stop().
(gdb) thr 1
[Switching to thread 1 (Thread 1)]#0 multi_cpu_stop (data=0xffff8802140d1da0) at kernel/stop_machine.c:192
192 if (msdata->state != curstate) {
Or is it I am missing something.
please let me know if .config need to be shared.
Quoted 23 lines
arch/x86/kernel/kvm.c +23
-0+local_irq_save (flags); + +/* +* check again make sure it didn't become free while +* we weren't looking. +*/ +if (ACCESS_ONCE (*ptr) != val) + goto out; + +/* +* halt until it's our turn and kicked. Note that we do safe halt +* for irq enabled case to avoid hang when lock info is overwritten +* in irq spinlock slowpath and no spurious interrupt occur to save us. +*/ +if (arch_irqs_disabled_flags (flags)) +halt (); +else +safe_halt (); + +out: + local_irq_restore(flags); +} +#endif /* QUEUE_SPINLOCK */
Peter Zijlstra
maintainerRe: [PATCH 11/11] qspinlock, kvm: Add paravirt support
Quoted 4 lines · Raghavendra K T
Quoted 2 lines · Peter Zijlstra
Signed-off-by Peter Zijlstra<[email protected]>---
[...]
Quoted 16 lines
arch/x86/kernel/kvm.c static void kvm_unlock_kick(struct arch_ · line 821
Quoted 12 lines
arch/x86/kernel/kvm.c +12
-0+ +void kvm_wait (int *ptr,int val) +{ +unsigned long flags; + +if (in_nmi ()) +return ; + +/* +* Make sure an interrupt handler can't upset things in a +* partially setup state. +*/ I am seeing hang with even 2 cpu guest (with patches on top of 3.15-rc6 ). looking further with gdb I see one cpu is stuck with
native_haltwith slowpathflag(_Q_LOCKED_SLOW)set when it was called.
Like said in 0/n I think, I only booted the kernel in kvm, didn't actually do anything with it.
It took me most of the day to figure out how to get paravirt working at all, didn't feel like spending another many hours trying to figure out how to make the crap thing do actual work.
But I'll see what I can do after we can 'conceptually' agree on the paravirt patch.
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Peter Zijlstra
maintainerRe: [PATCH 11/11] qspinlock, kvm: Add paravirt support
In reply to Raghavendra K TQuoted 4 lines · Raghavendra K T
Quoted 2 lines · Peter Zijlstra
Signed-off-by Peter Zijlstra<[email protected]>---
[...]
Quoted 16 lines
arch/x86/kernel/kvm.c static void kvm_unlock_kick(struct arch_ · line 821
Quoted 12 lines
arch/x86/kernel/kvm.c +12
-0+ +void kvm_wait (int *ptr,int val) +{ +unsigned long flags; + +if (in_nmi ()) +return ; + +/* +* Make sure an interrupt handler can't upset things in a +* partially setup state. +*/ I am seeing hang with even 2 cpu guest (with patches on top of 3.15-rc6 ). looking further with gdb I see one cpu is stuck with
native_haltwith slowpathflag(_Q_LOCKED_SLOW)set when it was called.
Like said in 0/n I think, I only booted the kernel in kvm, didn't actually do anything with it.
It took me most of the day to figure out how to get paravirt working at all, didn't feel like spending another many hours trying to figure out how to make the crap thing do actual work.
But I'll see what I can do after we can 'conceptually' agree on the paravirt patch.
_______________________________________________
Xen-devel mailing list
[email protected]
http://lists.xen.org/xen-devel
Re: [PATCH 11/11] qspinlock, kvm: Add paravirt support
In reply to Peter ZijlstraQuoted 2 lines · Peter Zijlstra
Signed-off-by Peter Zijlstra<[email protected]>---
[...]
arch/x86/kernel/kvm.c static void kvm_unlock_kick(struct arch_ · line 821
Quoted 12 lines
arch/x86/kernel/kvm.c +12
-0+ +void kvm_wait (int *ptr,int val) +{ +unsigned long flags; + +if (in_nmi ()) +return ; + +/* +* Make sure an interrupt handler can't upset things in a +* partially setup state. +*/
I am seeing hang with even 2 cpu guest (with patches on top of 3.15-rc6 ). looking further with gdb I see one cpu is stuck with native_halt with slowpath flag(_Q_LOCKED_SLOW) set when it was called.
(gdb) bt
#0 native_halt () at /test/master/arch/x86/include/asm/irqflags.h:55 #1 0xffffffff81033118 in halt (ptr=0xffffffff81eb0e58, val=524291) at /test/master/arch/x86/include/asm/paravirt.h:116 #2 kvm_wait (ptr=0xffffffff81eb0e58, val=524291) at arch/x86/kernel/kvm.c:835 #3 kvm_wait (ptr=0xffffffff81eb0e58, val=524291) at arch/x86/kernel/kvm.c:809 #4 0xffffffff810a2d8e in pv_wait (lock=0xffffffff81eb0e58) at /test/master/arch/x86/include/asm/paravirt.h:744 #5 __pv_wait_head (lock=0xffffffff81eb0e58) at kernel/locking/qspinlock.c:352
Value of lock seem to be 524288 (means already unlocked?) So apart from races Waiman mentioned, are we also in need of smp_mb() here and/or native_queue_unlock()?.
Interestingly I see other cpu stuck at multi_cpu_stop().
(gdb) thr 1
[Switching to thread 1 (Thread 1)]#0 multi_cpu_stop (data=0xffff8802140d1da0) at kernel/stop_machine.c:192
192 if (msdata->state != curstate) {
Or is it I am missing something.
please let me know if .config need to be shared.
Quoted 23 lines
arch/x86/kernel/kvm.c +23
-0+local_irq_save (flags); + +/* +* check again make sure it didn't become free while +* we weren't looking. +*/ +if (ACCESS_ONCE (*ptr) != val) + goto out; + +/* +* halt until it's our turn and kicked. Note that we do safe halt +* for irq enabled case to avoid hang when lock info is overwritten +* in irq spinlock slowpath and no spurious interrupt occur to save us. +*/ +if (arch_irqs_disabled_flags (flags)) +halt (); +else +safe_halt (); + +out: + local_irq_restore(flags); +} +#endif /* QUEUE_SPINLOCK */
PATCH 11/11 qspinlock, kvm: Add paravirt support
In reply to Peter Zijlstra--- arch/x86/kernel/kvm.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/Kconfig.locks | 2 - 2 files changed, 59 insertions(+), 1 deletion(-)
arch/x86/kernel/kvm.c +58 -0
@@ -569,6 +569,7 @@ static void kvm_kick_cpu(int cpu)kvm_hypercall2 (KVM_HC_KICK_CPU , flags, apicid); } +#ifndef CONFIG_QUEUE_SPINLOCK enum kvm_contention_stat {TAKEN_SLOW , TAKEN_SLOW_PICKUP,
@@ -796,6 +797,51 @@ static void kvm_unlock_kick(struct arch_ } } } +#else /* QUEUE_SPINLOCK */ + +#include <asm-generic/qspinlock.h> + +PV_CALLEE_SAVE_REGS_THUNK (__pv_init_node); +PV_CALLEE_SAVE_REGS_THUNK (__pv_link_and_wait_node); +PV_CALLEE_SAVE_REGS_THUNK (__pv_kick_node); + +PV_CALLEE_SAVE_REGS_THUNK (__pv_wait_head); +PV_CALLEE_SAVE_REGS_THUNK (__pv_queue_unlock); + +void kvm_wait (int *ptr,int val) +{ +unsigned long flags; + +if (in_nmi ()) +return ; + +/* +* Make sure an interrupt handler can't upset things in a +* partially setup state. +*/ +local_irq_save (flags); + +/* +* check again make sure it didn't become free while +* we weren't looking. +*/ +if (ACCESS_ONCE (*ptr) != val) + goto out; + +/* +* halt until it's our turn and kicked. Note that we do safe halt +* for irq enabled case to avoid hang when lock info is overwritten +* in irq spinlock slowpath and no spurious interrupt occur to save us. +*/ +if (arch_irqs_disabled_flags (flags)) +halt (); +else +safe_halt (); + +out: +local_irq_restore (flags); +} +#endif /* QUEUE_SPINLOCK */ /* * Setup pv_lock_ops to exploit KVM_FEATURE_PV_UNHALT if present.
@@ -808,8 +854,20 @@ void __init kvm_spinlock_init(void)if (!kvm_para_has_feature (KVM_FEATURE_PV_UNHALT ))return ; +#ifdef CONFIG_QUEUE_SPINLOCK + pv_lock_ops.init_node =PV_CALLEE_SAVE (__pv_init_node); + pv_lock_ops.link_and_wait_node =PV_CALLEE_SAVE (__pv_link_and_wait_node); + pv_lock_ops.kick_node =PV_CALLEE_SAVE (__pv_kick_node); + + pv_lock_ops.wait_head =PV_CALLEE_SAVE (__pv_wait_head); + pv_lock_ops.queue_unlock =PV_CALLEE_SAVE (__pv_queue_unlock); + + pv_lock_ops.wait = kvm_wait; + pv_lock_ops.kick = kvm_kick_cpu; +#else pv_lock_ops.lock_spinning =PV_CALLEE_SAVE (kvm_lock_spinning); pv_lock_ops.unlock_kick = kvm_unlock_kick; +#endif }static __init int kvm_spinlock_init_jump (void )
kernel/Kconfig.locks +1 -1
@@ -229,7 +229,7 @@ config ARCH_USE_QUEUE_SPINLOCK config QUEUE_SPINLOCK def_bool y if ARCH_USE_QUEUE_SPINLOCK - depends on SMP && !PARAVIRT_SPINLOCKS + depends on SMP && !(PARAVIRT_SPINLOCKS && XEN) config ARCH_USE_QUEUE_RWLOCK bool
PATCH 11/11 qspinlock, kvm: Add paravirt support
In reply to Peter Zijlstra--- arch/x86/kernel/kvm.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/Kconfig.locks | 2 - 2 files changed, 59 insertions(+), 1 deletion(-)
arch/x86/kernel/kvm.c +58 -0
@@ -569,6 +569,7 @@ static void kvm_kick_cpu(int cpu)kvm_hypercall2 (KVM_HC_KICK_CPU , flags, apicid); } +#ifndef CONFIG_QUEUE_SPINLOCK enum kvm_contention_stat {TAKEN_SLOW , TAKEN_SLOW_PICKUP,
@@ -796,6 +797,51 @@ static void kvm_unlock_kick(struct arch_ } } } +#else /* QUEUE_SPINLOCK */ + +#include <asm-generic/qspinlock.h> + +PV_CALLEE_SAVE_REGS_THUNK (__pv_init_node); +PV_CALLEE_SAVE_REGS_THUNK (__pv_link_and_wait_node); +PV_CALLEE_SAVE_REGS_THUNK (__pv_kick_node); + +PV_CALLEE_SAVE_REGS_THUNK (__pv_wait_head); +PV_CALLEE_SAVE_REGS_THUNK (__pv_queue_unlock); + +void kvm_wait (int *ptr,int val) +{ +unsigned long flags; + +if (in_nmi ()) +return ; + +/* +* Make sure an interrupt handler can't upset things in a +* partially setup state. +*/ +local_irq_save (flags); + +/* +* check again make sure it didn't become free while +* we weren't looking. +*/ +if (ACCESS_ONCE (*ptr) != val) + goto out; + +/* +* halt until it's our turn and kicked. Note that we do safe halt +* for irq enabled case to avoid hang when lock info is overwritten +* in irq spinlock slowpath and no spurious interrupt occur to save us. +*/ +if (arch_irqs_disabled_flags (flags)) +halt (); +else +safe_halt (); + +out: +local_irq_restore (flags); +} +#endif /* QUEUE_SPINLOCK */ /* * Setup pv_lock_ops to exploit KVM_FEATURE_PV_UNHALT if present.
@@ -808,8 +854,20 @@ void __init kvm_spinlock_init(void)if (!kvm_para_has_feature (KVM_FEATURE_PV_UNHALT ))return ; +#ifdef CONFIG_QUEUE_SPINLOCK + pv_lock_ops.init_node =PV_CALLEE_SAVE (__pv_init_node); + pv_lock_ops.link_and_wait_node =PV_CALLEE_SAVE (__pv_link_and_wait_node); + pv_lock_ops.kick_node =PV_CALLEE_SAVE (__pv_kick_node); + + pv_lock_ops.wait_head =PV_CALLEE_SAVE (__pv_wait_head); + pv_lock_ops.queue_unlock =PV_CALLEE_SAVE (__pv_queue_unlock); + + pv_lock_ops.wait = kvm_wait; + pv_lock_ops.kick = kvm_kick_cpu; +#else pv_lock_ops.lock_spinning =PV_CALLEE_SAVE (kvm_lock_spinning); pv_lock_ops.unlock_kick = kvm_unlock_kick; +#endif }static __init int kvm_spinlock_init_jump (void )
kernel/Kconfig.locks +1 -1
@@ -229,7 +229,7 @@ config ARCH_USE_QUEUE_SPINLOCK config QUEUE_SPINLOCK def_bool y if ARCH_USE_QUEUE_SPINLOCK - depends on SMP && !PARAVIRT_SPINLOCKS + depends on SMP && !(PARAVIRT_SPINLOCKS && XEN) config ARCH_USE_QUEUE_RWLOCK bool
Quoted 5 lines · Peter Zijlstra
Since Waiman seems incapable of doing simple things; here's my take on the paravirt crap.
The first few patches are taken from Waiman's latest series, but the virt support is completely new. Its primary aim is to not mess up the native code.
OK. I finally cleared some time to look over this and are reading the code in details to make sure I have it clear in mind. I will most likely ask some questions that are naive - hopefully they will lead to the code being self-explanatory for anybody else taking a stab at understanding them when bugs appear.
Quoted 3 lines
I've not stress tested it, but the virt and paravirt (kvm) cases boot on simple smp guests. I've not done Xen, but the patch should be simple and similar.
Looking forward to seeing it. Glancing over the KVM one and comparing it to the original version that Waiman posted it should be fairly simple. Perhaps even some of the code could be shared?
Quoted 15 lines
I ripped out all the unfair nonsense as its not at all required for paravirt and optimizations that make paravirt better at the cost of code clarity and/or native performance are just not worth it.
Also; if we were to ever add some of that unfair nonsense you do so _after_ you got the simple things working.
The thing I'm least sure about is the head tracking, I chose to do something different from what Waiman did, because his is
O(nr_cpus)and had the assumption that guests have smallnr_cpus. AFAIK this is not at all true. The biggest problem I have with what I did is that it contains wait loops itself.
Quoted 5 lines · Peter Zijlstra
Since Waiman seems incapable of doing simple things; here's my take on the paravirt crap.
The first few patches are taken from Waiman's latest series, but the virt support is completely new. Its primary aim is to not mess up the native code.
OK. I finally cleared some time to look over this and are reading the code in details to make sure I have it clear in mind. I will most likely ask some questions that are naive - hopefully they will lead to the code being self-explanatory for anybody else taking a stab at understanding them when bugs appear.
Quoted 3 lines
I've not stress tested it, but the virt and paravirt (kvm) cases boot on simple smp guests. I've not done Xen, but the patch should be simple and similar.
Looking forward to seeing it. Glancing over the KVM one and comparing it to the original version that Waiman posted it should be fairly simple. Perhaps even some of the code could be shared?
Quoted 15 lines
I ripped out all the unfair nonsense as its not at all required for paravirt and optimizations that make paravirt better at the cost of code clarity and/or native performance are just not worth it.
Also; if we were to ever add some of that unfair nonsense you do so _after_ you got the simple things working.
The thing I'm least sure about is the head tracking, I chose to do something different from what Waiman did, because his is
O(nr_cpus)and had the assumption that guests have smallnr_cpus. AFAIK this is not at all true. The biggest problem I have with what I did is that it contains wait loops itself.