Skip to contentSkip to conversation

futex: Introducing throughput-optimized (TP) futexes

PATCH New Waiman Long 2 participants 25 messages Mar 22, 2017 – Mar 24, 2017

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

v6 cover futex: Introducing throughput-optimized (TP) futexes

v5->v6:
- Futex code changes:
1) Allow userspace locking of exclusive locks instead of just kernel locking (patch 14). This is controlled by a flag passed to the futex(2) syscall. This enables better throughput.
2) Add a reader-preferring mode to shared locking.
- Futex microbenchmark improvements:
1) Using gcc __atomic builtins instead of the older __sync builtins. This allowed more optimized code for non-x86 architectures.
2) Add timeout support
3) Count # of times lock/unlock slowpath are invoked instead of the futex(2) syscalls.
- Include benchmark results on a Power8 system.
- The tp-futex.txt document was updated accordingly.
- Fix a couple of bugs in the v5 patch set.

v4->v5:
- Fix 0-day kernel build test compilation warnings.
- Extract out non-TP futexes futex-mutex and futex-rwlock microbenchmarks as separate patches, as suggested by Arnaldo.
- Rebased to the latest tip tree & use running_clock() instead of sched_clock().

v3->v4:
- Properly handle EFAULT error due to page fault.
- Extend the use cases of TP futexes to userspace rwlock.
- Change the lock handoff trigger from number of spinnings to elapsed time (5ms).
- Various updates to the "perf bench futex mutex" microbenchmark.
- Add a new "perf bench futex rwlock" microbenchmark for measuring rwlock performance.
- Add mutex_owner to futex_state object to hold the serialization mutex owner.
- Streamline a number of helper functions and other miscellenous coding improvements.
- Rebase the patchset to the 4.10 kernel.

v2->v3:
- Use the abbreviation TP for the new futexes instead of TO.
- Make a number of changes accordingly to review comments from ThomasG, PeterZ and MikeG.
- Breaks the main futex patch into smaller pieces to make them easier to review.

v1->v2:
- Adds an explicit lock hand-off mechanism.
- Adds timeout support.
- Simplifies the required userspace code.
- Fixes a number of problems in the v1 code.

This patchset introduces a new futex implementation called throughput-optimized (TP) futexes. It is similar to PI futexes in its calling convention, but provides better throughput than the wait-wake (WW) futexes by encouraging lock stealing and optimistic spinning.

The new TP futexes can be used in implementing both userspace mutexes and rwlocks. They provides better performance while simplifying the userspace locking implementation at the same time. The WW futexes are still needed to implement other synchronization primitives like conditional variables and semaphores that cannot be handled by the TP futexes.

Another advantage of TP futexes is that it has a built-in lock handoff mechanism to prevent lock starvation from happenning as long as the underlying kernel mutex doesn't have lock starvation problem.

Patches 1-2 implements userspace exclusive lock and read/write lock benchmark in perf bench using futexes.

Patches 3-8 are preparatory patches that pave the way to implement the TP futexes.

Patch 9 implements the basic TP futex that can support userspace mutexes.

Patch 10 adds robust handling to TP futex to handle the death of TP futex exclusive lock owners.

Patch 11 adds a lock hand-off mechanism that can prevent lock starvation to happen while introducing minimal runtime overhead.

Patch 12 enables the FUTEX_LOCK futex(2) syscall to return status information, such as how the lock is acquired and how many time the task needs to sleep in the spinning loop, that can be used by userspace utilities to monitor how the TP futexes are performing.

Patch 13 enables userspace applications to supply a timeout value to abort the lock acquisition attempt after the specified time period has passed.

Patch 14 enables users to choose to do userspace locking of exclusive locks by returning the EAGAIN error code instead of doing that in the kernel.

Patch 15 adds a new document tp-futex.txt in the Documentation directory to describe the new TP futexes.

Patch 16 extends the TP futexes to support userspace rwlocks.

Patch 17 enables more reader lock stealing of TP futexes in the kernel.

Patch 18 groups readers together as a spin group to enhance reader throughput.

Patch 19 updates the tp-futex.txt file to add information about rwlock support.

Patch 20 extends the perf bench futex locking benchmark to include variants using the new TP futexes.

Patch 21 updates the wake_up_q() function to returns the number woken tasks.

Patch 22 enables the dumping of internal futex state information via debugfs.

All the benchmark results shown in the change logs were produced by the microbenchmarks included in this patchset. So everyone can run the microbenchmark to see how the TP futexes perform and behave in their own test systems.

Once this patchset is finalized, an updated manpage patch to document the new TP futexes will be sent out. The next step will then be to make Glibc NTPL use the new TP futexes.

Performance highlight in term of average locking rates (ops/sec) on a 2-socket x86-64 system are as follows:

                WW futex       TP futex      Glibc
                --------       --------      -----
  mutex         136,291        158,897      110,186
  rwlock         86,004        138,280       41,267

Patches 15 and 19 contain more detailed information about the performance characteristics of the TP futexes when implementing userspace mutex and rwlock respectively when compared with other possible ways of doing so via the wait-wake futexes.

Waiman Long (22):
perf bench: New microbenchmark for userspace mutex performance perf bench: New microbenchmark for userspace rwlock performance
futex: Consolidate duplicated timer setup code
futex: Rename futex_pi_state to futex_state
futex: Add helpers to get & cmpxchg futex value without lock
futex: Consolidate pure pi_state_list add & delete codes to helpers
futex: Add a new futex type field into futex_state
futex: Allow direct attachment of futex_state objects to hash bucket
futex: Introduce throughput-optimized (TP) futexes
TP-futex: Enable robust handling
TP-futex: Implement lock handoff to prevent lock starvation
TP-futex: Return status code on FUTEX_LOCK calls
TP-futex: Add timeout support
TP-futex: Optionally return EAGAIN for userspace locking TP-futex, doc: Add TP futexes documentation
TP-futex: Support userspace reader/writer locks
TP-futex: Enable kernel reader lock stealing
TP-futex: Group readers together in wait queue
TP-futex, doc: Update TP futexes document on shared locking perf bench: Extend mutex/rwlock futex suite to test TP futexes sched, TP-futex: Make wake_up_q() return wakeup count
futex: Dump internal futex state via debugfs

 Documentation/00-INDEX                  |    2 +
 Documentation/tp-futex.txt              |  310 ++++++
 include/linux/sched.h                   |    4 +-
 include/linux/sched/wake_q.h            |    2 +-
 include/uapi/linux/futex.h              |   32 +-
 kernel/futex.c                          | 1436 ++++++++++++++++++++++++--
 kernel/sched/core.c                     |    6 +-
 tools/perf/Documentation/perf-bench.txt |    5 +
 tools/perf/bench/Build                  |    1 +
 tools/perf/bench/bench.h                |    2 +
 tools/perf/bench/futex-locks.c          | 1680 +++++++++++++++++++++++++++++++
 tools/perf/bench/futex.h                |   45 +
 tools/perf/builtin-bench.c              |   11 +
 tools/perf/check-headers.sh             |    4 +
 14 files changed, 3428 insertions(+), 112 deletions(-)
 create mode 100644 Documentation/tp-futex.txt
 create mode 100644 tools/perf/bench/futex-locks.c
Signature · 1 line
1.8.3.1
#1 lore raw

PATCH v6 1/22 perf bench: New microbenchmark for userspace mutex performance

This microbenchmark simulates how the use of different futex types can affect the actual performanace of userspace mutex locks. The usage is:

perf bench futex mutex <options>

Three sets of simple mutex lock and unlock functions are implemented using the wait-wake and PI futexes as well as the glibc mutex respectively. This microbenchmark then runs the locking rate measurement tests using either one of those mutexes or all of them consecutively.

An example output from this microbenchmark was as follows:

[PID 19203]: 36 threads doing WW futex lockings (load=5) for 10 secs.

Locking statistics:

  Test run time                = 10.00s
  Total exclusive locking ops  = 40,341,750
  Exclusive lock slowpaths     = 9,641,061
  Exclusive unlock slowpaths   = 13,168,413
  EAGAIN lock errors           = 9,032,324
  Process wakeups              = 1,733,311

Percentages:

  Exclusive lock slowpaths     = 23.9%
  Exclusive unlock slowpaths   = 32.6%
  EAGAIN lock errors           = 93.7%
  Process wakeups              = 13.2%

Per-thread Locking Rates:
Avg = 112,057 ops/sec (+- 0.56%)
Min = 105,377 ops/sec
Max = 119,632 ops/sec

---
 tools/perf/Documentation/perf-bench.txt |   2 +
 tools/perf/bench/Build                  |   1 +
 tools/perf/bench/bench.h                |   1 +
 tools/perf/bench/futex-locks.c          | 815 ++++++++++++++++++++++++++++++++
 tools/perf/builtin-bench.c              |  10 +
 5 files changed, 829 insertions(+)
 create mode 100644 tools/perf/bench/futex-locks.c
tools/perf/Documentation/perf-bench.txt +2 -0
index 34750fc..1fa5a74 100644
@@ -203,6 +203,8 @@ Suite for evaluating requeue calls.
 *lock-pi*::
 Suite for evaluating futex lock_pi calls.
 
+*mutex*::
+Suite for evaluating futex calls for implementing userspace mutexes.
 
 SEE ALSO
 --------
tools/perf/bench/Build +1 -0
index 60bf119..7ce1cd7 100644
@@ -6,6 +6,7 @@ perf-y += futex-wake.o
 perf-y += futex-wake-parallel.o
 perf-y += futex-requeue.o
 perf-y += futex-lock-pi.o
+perf-y += futex-locks.o
 
 perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-asm.o
 perf-$(CONFIG_X86_64) += mem-memset-x86-64-asm.o
tools/perf/bench/bench.h +1 -0
index 579a592..b0632df 100644
@@ -36,6 +36,7 @@
 int bench_futex_requeue(int argc, const char **argv, const char *prefix);
 /* pi futexes */
 int bench_futex_lock_pi(int argc, const char **argv, const char *prefix);
+int bench_futex_mutex(int argc, const char **argv, const char *prefix);
 
 #define BENCH_FORMAT_DEFAULT_STR	"default"
 #define BENCH_FORMAT_DEFAULT		0
tools/perf/bench/futex-locks.c new file +815 -0

815 lines not on this page · raw message

tools/perf/builtin-bench.c +10 -0
index a1cddc6..bf4418d 100644
@@ -20,6 +20,7 @@
 #include "builtin.h"
 #include "bench/bench.h"
 
+#include <locale.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -62,6 +63,7 @@ struct bench {
 	{ "requeue",	"Benchmark for futex requeue calls",            bench_futex_requeue	},
 	/* pi-futexes */
 	{ "lock-pi",	"Benchmark for futex lock_pi calls",            bench_futex_lock_pi	},
+	{ "mutex",	"Benchmark for mutex locks using futexes",	bench_futex_mutex	},
 	{ "all",	"Run all futex benchmarks",			NULL			},
 	{ NULL,		NULL,						NULL			}
 };
@@ -215,6 +217,7 @@ int cmd_bench(int argc, const char **argv, const char *prefix __maybe_unused)
 {
 	struct collection *coll;
 	int ret = 0;
+	char *locale;
 
 	if (argc < 2) {
 		/* No collection specified. */
@@ -222,6 +225,13 @@ int cmd_bench(int argc, const char **argv, const char *prefix __maybe_unused)
 		goto end;
 	}
 
+	/*
+	 * Enable better number formatting.
+	 */
+	locale = setlocale(LC_NUMERIC, "");
+	if (!strcmp(locale, "C"))
+		setlocale(LC_NUMERIC, "en_US");
+
 	argc = parse_options(argc, argv, bench_options, bench_usage,
 			     PARSE_OPT_STOP_AT_NON_OPTION);
 
Signature · 1 line
1.8.3.1
#2 lore raw

PATCH v6 2/22 perf bench: New microbenchmark for userspace rwlock performance

In reply to Waiman Long

This microbenchmark simulates how the use of different futex types can affect the actual performanace of userspace rwlock locks. The usage is:

perf bench futex rwlock <options>

Two sets of simple rwlock lock and unlock functions are implemented using the wait-wake futexes and glibc rwlock respectively. This microbenchmark then runs the locking rate measurement tests using either one of those rwlocks or all of them consecutively.

Sample output from this microbenchmark was as follows:

[PID 19333]: 36 threads doing WW futex lockings (load=1) for 10 secs.

Locking statistics:

  Test run time                = 10.00s
  Total exclusive locking ops  = 16,892,644
  Total shared locking ops     = 16,892,644
  Exclusive lock slowpaths     = 13,737,148
  Exclusive unlock slowpaths   = 9,792,528
  Shared lock slowpaths        = 1,922,379
  Shared unlock slowpaths      = 4,218,813
  EAGAIN lock errors           = 15,433,105
  Process wakeups              = 226,422

Percentages:

  Exclusive lock slowpaths     = 81.3%
  Shared lock slowpaths        = 11.4%
  Exclusive unlock slowpaths   = 58.0%
  Shared unlock slowpaths      = 25.0%
  EAGAIN lock errors           = 98.6%
  Process wakeups              = 1.6%

Shared Lock Batch Stats:

  Total shared lock batches    = 10,569,959
  Avg batch size               = 1.6
  Max batch size               = 26

Per-thread Locking Rates:
Avg = 93,841 ops/sec (+- 0.40%)
Min = 89,269 ops/sec
Max = 101,021 ops/sec

---
 tools/perf/Documentation/perf-bench.txt |   3 +
 tools/perf/bench/bench.h                |   1 +
 tools/perf/bench/futex-locks.c          | 668 +++++++++++++++++++++++++++++++-
 tools/perf/builtin-bench.c              |   1 +
 4 files changed, 654 insertions(+), 19 deletions(-)
tools/perf/Documentation/perf-bench.txt +3 -0
index 1fa5a74..e5a7079 100644
@@ -206,6 +206,9 @@ Suite for evaluating futex lock_pi calls.
 *mutex*::
 Suite for evaluating futex calls for implementing userspace mutexes.
 
+*rwlock*::
+Suite for evaluating futex calls for implementing userspace rwlocks.
+
 SEE ALSO
 --------
 linkperf:perf[1]
tools/perf/bench/bench.h +1 -0
index b0632df..a7e2037 100644
@@ -37,6 +37,7 @@
 /* pi futexes */
 int bench_futex_lock_pi(int argc, const char **argv, const char *prefix);
 int bench_futex_mutex(int argc, const char **argv, const char *prefix);
+int bench_futex_rwlock(int argc, const char **argv, const char *prefix);
 
 #define BENCH_FORMAT_DEFAULT_STR	"default"
 #define BENCH_FORMAT_DEFAULT		0
tools/perf/bench/futex-locks.c +649 -19

845 lines not on this page · raw message

tools/perf/builtin-bench.c +1 -0
index bf4418d..424a014 100644
@@ -64,6 +64,7 @@ struct bench {
 	/* pi-futexes */
 	{ "lock-pi",	"Benchmark for futex lock_pi calls",            bench_futex_lock_pi	},
 	{ "mutex",	"Benchmark for mutex locks using futexes",	bench_futex_mutex	},
+	{ "rwlock",	"Benchmark for rwlocks using futexes",		bench_futex_rwlock	},
 	{ "all",	"Run all futex benchmarks",			NULL			},
 	{ NULL,		NULL,						NULL			}
 };
Signature · 1 line
1.8.3.1
#3 lore raw

PATCH v6 3/22 futex: Consolidate duplicated timer setup code

In reply to Waiman Long

A new futex_setup_timer() helper function is added to consolidate all the hrtimer_sleeper setup code.

---
 kernel/futex.c | 67 ++++++++++++++++++++++++++++++++--------------------------
 1 file changed, 37 insertions(+), 30 deletions(-)
kernel/futex.c +37 -30
index 6ee1a8e..c8f8c5a 100644
@@ -479,6 +479,35 @@ static void drop_futex_key_refs(union futex_key *key)
 }
 
 /**
+ * futex_setup_timer - set up the sleeping hrtimer.
+ * @time:	ptr to the given timeout value
+ * @timeout:	the hrtimer_sleeper structure to be set up
+ * @flags:	futex flags
+ * @range_ns:	optional range in ns
+ *
+ * Return: Initialized hrtimer_sleeper structure or NULL if no timeout
+ *	   value given
+ */
+static inline struct hrtimer_sleeper *
+futex_setup_timer(ktime_t *time, struct hrtimer_sleeper *timeout,
+		  int flags, u64 range_ns)
+{
+	if (!time)
+		return NULL;
+
+	hrtimer_init_on_stack(&timeout->timer, (flags & FLAGS_CLOCKRT) ?
+			      CLOCK_REALTIME : CLOCK_MONOTONIC,
+			      HRTIMER_MODE_ABS);
+	hrtimer_init_sleeper(timeout, current);
+	if (range_ns)
+		hrtimer_set_expires_range_ns(&timeout->timer, *time, range_ns);
+	else
+		hrtimer_set_expires(&timeout->timer, *time);
+
+	return timeout;
+}
+
+/**
  * get_futex_key() - Get parameters which are the keys for a futex
  * @uaddr:	virtual address of the futex
  * @fshared:	0 for a PROCESS_PRIVATE futex, 1 for PROCESS_SHARED
@@ -2404,7 +2433,7 @@ static int futex_wait_setup(u32 __user *uaddr, u32 val, unsigned int flags,
 static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
 		      ktime_t *abs_time, u32 bitset)
 {
-	struct hrtimer_sleeper timeout, *to = NULL;
+	struct hrtimer_sleeper timeout, *to;
 	struct restart_block *restart;
 	struct futex_hash_bucket *hb;
 	struct futex_q q = futex_q_init;
@@ -2414,17 +2443,8 @@ static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
 		return -EINVAL;
 	q.bitset = bitset;
 
-	if (abs_time) {
-		to = &timeout;
-
-		hrtimer_init_on_stack(&to->timer, (flags & FLAGS_CLOCKRT) ?
-				      CLOCK_REALTIME : CLOCK_MONOTONIC,
-				      HRTIMER_MODE_ABS);
-		hrtimer_init_sleeper(to, current);
-		hrtimer_set_expires_range_ns(&to->timer, *abs_time,
-					     current->timer_slack_ns);
-	}
-
+	to = futex_setup_timer(abs_time, &timeout, flags,
+			       current->timer_slack_ns);
 retry:
 	/*
 	 * Prepare to wait on uaddr. On success, holds hb lock and increments
@@ -2504,7 +2524,7 @@ static long futex_wait_restart(struct restart_block *restart)
 static int futex_lock_pi(u32 __user *uaddr, unsigned int flags,
 			 ktime_t *time, int trylock)
 {
-	struct hrtimer_sleeper timeout, *to = NULL;
+	struct hrtimer_sleeper timeout, *to;
 	struct futex_hash_bucket *hb;
 	struct futex_q q = futex_q_init;
 	int res, ret;
@@ -2512,13 +2532,7 @@ static int futex_lock_pi(u32 __user *uaddr, unsigned int flags,
 	if (refill_pi_state_cache())
 		return -ENOMEM;
 
-	if (time) {
-		to = &timeout;
-		hrtimer_init_on_stack(&to->timer, CLOCK_REALTIME,
-				      HRTIMER_MODE_ABS);
-		hrtimer_init_sleeper(to, current);
-		hrtimer_set_expires(&to->timer, *time);
-	}
+	to = futex_setup_timer(time, &timeout, FLAGS_CLOCKRT, 0);
 
 retry:
 	ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &q.key, VERIFY_WRITE);
@@ -2813,7 +2827,7 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,
 				 u32 val, ktime_t *abs_time, u32 bitset,
 				 u32 __user *uaddr2)
 {
-	struct hrtimer_sleeper timeout, *to = NULL;
+	struct hrtimer_sleeper timeout, *to;
 	struct rt_mutex_waiter rt_waiter;
 	struct futex_hash_bucket *hb;
 	union futex_key key2 = FUTEX_KEY_INIT;
@@ -2826,15 +2840,8 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,
 	if (!bitset)
 		return -EINVAL;
 
-	if (abs_time) {
-		to = &timeout;
-		hrtimer_init_on_stack(&to->timer, (flags & FLAGS_CLOCKRT) ?
-				      CLOCK_REALTIME : CLOCK_MONOTONIC,
-				      HRTIMER_MODE_ABS);
-		hrtimer_init_sleeper(to, current);
-		hrtimer_set_expires_range_ns(&to->timer, *abs_time,
-					     current->timer_slack_ns);
-	}
+	to = futex_setup_timer(abs_time, &timeout, flags,
+			       current->timer_slack_ns);
 
 	/*
 	 * The waiter is allocated on our stack, manipulated by the requeue
Signature · 1 line
1.8.3.1
#4 lore raw

PATCH v6 4/22 futex: Rename futex_pi_state to futex_state

In reply to Waiman Long

The futex_pi_state structure will be overloaded in later patches to store state information about non-PI futexes. So the structure name itself is no longer a good description of its purpose. So its name is changed to futex_state, a more generic name.

Some of the functions that process the futex states are also renamed.

---
 include/linux/sched.h |   4 +-
 kernel/futex.c        | 107 +++++++++++++++++++++++++-------------------------
 2 files changed, 56 insertions(+), 55 deletions(-)
include/linux/sched.h +2 -2
index d67eee8..e857b6f 100644
@@ -34,7 +34,7 @@
 struct blk_plug;
 struct cfs_rq;
 struct fs_struct;
-struct futex_pi_state;
+struct futex_state;
 struct io_context;
 struct mempolicy;
 struct nameidata;
@@ -867,7 +867,7 @@ struct task_struct {
 	struct compat_robust_list_head __user *compat_robust_list;
 #endif
 	struct list_head		pi_state_list;
-	struct futex_pi_state		*pi_state_cache;
+	struct futex_state		*pi_state_cache;
 #endif
 #ifdef CONFIG_PERF_EVENTS
 	struct perf_event_context	*perf_event_ctxp[perf_nr_task_contexts];
kernel/futex.c +54 -53

271 lines not on this page · raw message

Signature · 1 line
1.8.3.1
#5 lore raw

PATCH v6 5/22 futex: Add helpers to get & cmpxchg futex value without lock

In reply to Waiman Long

Two new helper functions cmpxchg_futex_value() and get_futex_value() are added to access and change the futex value without the hash bucket lock. As a result, page fault is enabled and the page will be faulted in if not present yet.

---
 kernel/futex.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
kernel/futex.c +15 -0
index 20aed91..9a27a79 100644
@@ -805,6 +805,21 @@ static int get_futex_value_locked(u32 *dest, u32 __user *from)
 	return ret ? -EFAULT : 0;
 }
 
+/*
+ * The equivalents of the above cmpxchg_futex_value_locked() and
+ * get_futex_value_locked which are called without the hash bucket lock
+ * and so can have page fault enabled.
+ */
+static inline int cmpxchg_futex_value(u32 *curval, u32 __user *uaddr,
+				      u32 uval, u32 newval)
+{
+	return futex_atomic_cmpxchg_inatomic(curval, uaddr, uval, newval);
+}
+
+static inline int get_futex_value(u32 *dest, u32 __user *from)
+{
+	return __get_user(*dest, from);
+}
 
 /*
  * PI code:
Signature · 1 line
1.8.3.1
#6 lore raw

PATCH v6 6/22 futex: Consolidate pure pi_state_list add & delete codes to helpers

In reply to Waiman Long

Two new helper functions (task_pi_list_add & task_pi_list_del) are created to consolidate all the pure pi_state_list addition and insertion codes. The set_owner argument in task_pi_list_add() will be needed in a later patch.

---
 kernel/futex.c | 52 +++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 37 insertions(+), 15 deletions(-)
kernel/futex.c +37 -15
index 9a27a79..cff711d 100644
@@ -824,6 +824,39 @@ static inline int get_futex_value(u32 *dest, u32 __user *from)
 /*
  * PI code:
  */
+
+/**
+ * task_pi_list_add - add futex state object to a task's pi_state_list
+ * @task : task structure
+ * @state: futex state object
+ */
+static inline void task_pi_list_add(struct task_struct *task,
+				    struct futex_state *state)
+{
+	raw_spin_lock_irq(&task->pi_lock);
+	WARN_ON(!list_empty(&state->list));
+	list_add(&state->list, &task->pi_state_list);
+	state->owner = task;
+	raw_spin_unlock_irq(&task->pi_lock);
+}
+
+/**
+ * task_pi_list_del - delete futex state object from a task's pi_state_list
+ * @state: futex state object
+ * @warn : warn if list is empty when set
+ */
+static inline void task_pi_list_del(struct futex_state *state, const bool warn)
+{
+	struct task_struct *task = state->owner;
+
+	raw_spin_lock_irq(&task->pi_lock);
+	if (warn)
+		WARN_ON(list_empty(&state->list));
+	list_del_init(&state->list);
+	state->owner = NULL;
+	raw_spin_unlock_irq(&task->pi_lock);
+}
+
 static int refill_futex_state_cache(void)
 {
 	struct futex_state *state;
@@ -876,9 +909,7 @@ static void put_futex_state(struct futex_state *state)
 	 * and has cleaned up the futex state already
 	 */
 	if (state->owner) {
-		raw_spin_lock_irq(&state->owner->pi_lock);
-		list_del_init(&state->list);
-		raw_spin_unlock_irq(&state->owner->pi_lock);
+		task_pi_list_del(state, false);
 
 		rt_mutex_proxy_unlock(&state->pi_mutex, state->owner);
 	}
@@ -2213,19 +2244,10 @@ static int fixup_pi_state_owner(u32 __user *uaddr, struct futex_q *q,
 	 * We fixed up user space. Now we need to fix the pi_state
 	 * itself.
 	 */
-	if (pi_state->owner != NULL) {
-		raw_spin_lock_irq(&pi_state->owner->pi_lock);
-		WARN_ON(list_empty(&pi_state->list));
-		list_del_init(&pi_state->list);
-		raw_spin_unlock_irq(&pi_state->owner->pi_lock);
-	}
+	if (pi_state->owner != NULL)
+		task_pi_list_del(pi_state, true);
 
-	pi_state->owner = newowner;
-
-	raw_spin_lock_irq(&newowner->pi_lock);
-	WARN_ON(!list_empty(&pi_state->list));
-	list_add(&pi_state->list, &newowner->pi_state_list);
-	raw_spin_unlock_irq(&newowner->pi_lock);
+	task_pi_list_add(newowner, pi_state);
 	return 0;
 
 	/*
Signature · 1 line
1.8.3.1
#7 lore raw

PATCH v6 7/22 futex: Add a new futex type field into futex_state

In reply to Waiman Long

As the futex_state structure will be overloaded in later patches to be used by non-PI futexes, it is necessary to add a type field to distinguish among different types of futexes.

---
 kernel/futex.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)
kernel/futex.c +11 -4
index cff711d..fd365b2 100644
@@ -193,6 +193,10 @@
 #define FLAGS_CLOCKRT		0x02
 #define FLAGS_HAS_TIMEOUT	0x04
 
+enum futex_type {
+	TYPE_PI = 0,
+};
+
 /*
  * Futex state object:
  *  - Priority Inheritance state
@@ -212,6 +216,7 @@ struct futex_state {
 	struct task_struct *owner;
 	atomic_t refcount;
 
+	enum futex_type type;
 	union futex_key key;
 };
 
@@ -905,13 +910,14 @@ static void put_futex_state(struct futex_state *state)
 		return;
 
 	/*
-	 * If state->owner is NULL, the owner is most probably dying
-	 * and has cleaned up the futex state already
+	 * If state->owner is NULL and the type is TYPE_PI, the owner is
+	 * most probably dying and has cleaned up the futex state already.
 	 */
 	if (state->owner) {
 		task_pi_list_del(state, false);
 
-		rt_mutex_proxy_unlock(&state->pi_mutex, state->owner);
+		if (state->type == TYPE_PI)
+			rt_mutex_proxy_unlock(&state->pi_mutex, state->owner);
 	}
 
 	if (current->pi_state_cache)
@@ -1064,7 +1070,7 @@ static int attach_to_pi_state(u32 uval, struct futex_state *pi_state,
 	/*
 	 * Userspace might have messed up non-PI and PI futexes [3]
 	 */
-	if (unlikely(!pi_state))
+	if (unlikely(!pi_state || (pi_state->type != TYPE_PI)))
 		return -EINVAL;
 
 	WARN_ON(!atomic_read(&pi_state->refcount));
@@ -1182,6 +1188,7 @@ static int attach_to_pi_owner(u32 uval, union futex_key *key,
 
 	/* Store the key for possible exit cleanups: */
 	pi_state->key = *key;
+	pi_state->type = TYPE_PI;
 
 	WARN_ON(!list_empty(&pi_state->list));
 	list_add(&pi_state->list, &p->pi_state_list);
Signature · 1 line
1.8.3.1
#8 lore raw

PATCH v6 8/22 futex: Allow direct attachment of futex_state objects to hash bucket

In reply to Waiman Long

Currently, the futex state objects can only be located indirectly as

hash bucket => futex_q => futex state

Actually it can be beneficial in some cases to locate the futex state object directly from the hash bucket without the futex_q middleman. Therefore, a new list head to link the futex state objects as well as a new spinlock to manage them are added to the hash bucket.

To limit size increase for UP systems, these new fields are only for SMP machines where the cacheline alignment of the hash bucket leaves it with enough empty space for the new fields.

---
 kernel/futex.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)
kernel/futex.c +24 -0
index fd365b2..eb15095 100644
@@ -209,6 +209,11 @@ struct futex_state {
 	struct list_head list;
 
 	/*
+	 * Can link to fs_head in the owning hash bucket.
+	 */
+	struct list_head fs_list;
+
+	/*
 	 * The PI object:
 	 */
 	struct rt_mutex pi_mutex;
@@ -264,11 +269,24 @@ struct futex_q {
  * Hash buckets are shared by all the futex_keys that hash to the same
  * location.  Each key may have multiple futex_q structures, one for each task
  * waiting on a futex.
+ *
+ * Alternatively (in SMP), a key can be associated with a unique futex_state
+ * object where multiple waiters waiting for that futex can queue up in that
+ * futex_state object without using the futex_q structure. A separate
+ * futex_state lock (fs_lock) is used for processing those futex_state objects.
  */
 struct futex_hash_bucket {
 	atomic_t waiters;
 	spinlock_t lock;
 	struct plist_head chain;
+
+#ifdef CONFIG_SMP
+	/*
+	 * Fields for managing futex_state object list
+	 */
+	spinlock_t fs_lock;
+	struct list_head fs_head;
+#endif
 } ____cacheline_aligned_in_smp;
 
 /*
@@ -875,6 +893,8 @@ static int refill_futex_state_cache(void)
 		return -ENOMEM;
 
 	INIT_LIST_HEAD(&state->list);
+	INIT_LIST_HEAD(&state->fs_list);
+
 	/* pi_mutex gets initialized later */
 	state->owner = NULL;
 	atomic_set(&state->refcount, 1);
@@ -3377,6 +3397,10 @@ static int __init futex_init(void)
 		atomic_set(&futex_queues[i].waiters, 0);
 		plist_head_init(&futex_queues[i].chain);
 		spin_lock_init(&futex_queues[i].lock);
+#ifdef CONFIG_SMP
+		INIT_LIST_HEAD(&futex_queues[i].fs_head);
+		spin_lock_init(&futex_queues[i].fs_lock);
+#endif
 	}
 
 	return 0;
Signature · 1 line
1.8.3.1
#9 lore raw

PATCH v6 9/22 futex: Introduce throughput-optimized (TP) futexes

In reply to Waiman Long

A new futex implementation called throughput-optimized (TP) futexes is introduced. The goal of this new futex type is to maximize locking throughput at the expense of fairness and deterministic latency. Its throughput is higher than that of the wait-wake futexes especially on systems with a large number of CPUs and for workloads where the lock owners are unlikely to sleep. The downside of this new futex type is the increase in the response time variance.

The new TP futex type is designed to be used for mutex-like exclusive user space locks. It is not as versatile as the wait-wake futexes which can be used for other locking primitives like conditional variables, or semaphores. So it is not a direct replacement of wait-wake futexes.

---
 include/uapi/linux/futex.h |   4 +
 kernel/futex.c             | 569 ++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 570 insertions(+), 3 deletions(-)
include/uapi/linux/futex.h +4 -0
index 0b1f716..7f676ac 100644
@@ -20,6 +20,8 @@
 #define FUTEX_WAKE_BITSET	10
 #define FUTEX_WAIT_REQUEUE_PI	11
 #define FUTEX_CMP_REQUEUE_PI	12
+#define FUTEX_LOCK		13
+#define FUTEX_UNLOCK		14
 
 #define FUTEX_PRIVATE_FLAG	128
 #define FUTEX_CLOCK_REALTIME	256
@@ -39,6 +41,8 @@
 					 FUTEX_PRIVATE_FLAG)
 #define FUTEX_CMP_REQUEUE_PI_PRIVATE	(FUTEX_CMP_REQUEUE_PI | \
 					 FUTEX_PRIVATE_FLAG)
+#define FUTEX_LOCK_PRIVATE	(FUTEX_LOCK | FUTEX_PRIVATE_FLAG)
+#define FUTEX_UNLOCK_PRIVATE	(FUTEX_UNLOCK | FUTEX_PRIVATE_FLAG)
 
 /*
  * Support for robust futexes: the kernel cleans up held futexes at
kernel/futex.c +566 -3

636 lines not on this page · raw message

Signature · 1 line
1.8.3.1
#10 lore raw

PATCH v6 10/22 TP-futex: Enable robust handling

In reply to Waiman Long

The TP futexes don't have code to handle the death of futex owners. There are 2 different cases that need to be considered.

As top waiter gets a reference to the task structure of the futex owner, the task structure will never go away even if the owner dies. When the futex owner died while the top waiter is spinning, the task structure will be marked dead or the pid won't have a matching task structure if the task died before a reference is taken. Alternatively, if robust futex attribute is enabled, the FUTEX_OWNER_DIED bit of the futex word may also be set. In all those cases, what the top waiter need to do is to grab the futex directly. An informational message will be printed to highlight this event.

If the futex owner died while the top waiter is sleeping, we need to make the exit processing code to wake up the top waiter. This is done by chaining the futex state object into the pi_state_list of the futex owner before the top waiter sleeps so that if exit_pi_state_list() is called, the wakeup will happen. The top waiter needs to remove its futex state object from the pi_state_list of the old owner if the ownership changes hand or when the lock is acquired.

---
 kernel/futex.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 79 insertions(+), 6 deletions(-)
kernel/futex.c +79 -6
index 7270552..af367e8 100644
@@ -1006,7 +1006,7 @@ static struct task_struct * futex_find_get_task(pid_t pid)
 }
 
 /*
- * This task is holding PI mutexes at exit time => bad.
+ * This task is holding PI or TP mutexes at exit time => bad.
  * Kernel cleans up PI-state, but userspace is likely hosed.
  * (Robust-futex cleanup is separate and might save the day for userspace.)
  */
@@ -1023,12 +1023,31 @@ void exit_pi_state_list(struct task_struct *curr)
 	 * We are a ZOMBIE and nobody can enqueue itself on
 	 * pi_state_list anymore, but we have to be careful
 	 * versus waiters unqueueing themselves:
+	 *
+	 * For TP futexes, the only purpose of showing up in the
+	 * pi_state_list is for this function to wake up the serialization
+	 * mutex owner (state->mutex_owner). We don't actually need to take
+	 * the HB lock. The futex state and task struct won't go away as long
+	 * as we hold the pi_lock.
 	 */
 	raw_spin_lock_irq(&curr->pi_lock);
 	while (!list_empty(head)) {
 
 		next = head->next;
 		pi_state = list_entry(next, struct futex_state, list);
+
+		if (pi_state->type == TYPE_TP) {
+			struct task_struct *owner;
+
+			owner = READ_ONCE(pi_state->mutex_owner);
+			WARN_ON(list_empty(&pi_state->list));
+			list_del_init(&pi_state->list);
+			pi_state->owner = NULL;
+			if (owner)
+				wake_up_process(owner);
+			continue;
+		}
+
 		key = pi_state->key;
 		hb = hash_futex(&key);
 		raw_spin_unlock_irq(&curr->pi_lock);
@@ -3197,8 +3216,8 @@ int handle_futex_death(u32 __user *uaddr, struct task_struct *curr, int pi)
 			goto retry;
 
 		/*
-		 * Wake robust non-PI futexes here. The wakeup of
-		 * PI futexes happens in exit_pi_state():
+		 * Wake robust wait-wake futexes here. The wakeup of
+		 * PI and TP futexes happens in exit_pi_state():
 		 */
 		if (!pi && (uval & FUTEX_WAITERS))
 			futex_wake(uaddr, 1, 1, FUTEX_BITSET_MATCH_ANY);
@@ -3339,6 +3358,12 @@ void exit_robust_list(struct task_struct *curr)
  * Unlike the other futexes, the futex_q structures aren't used. Instead,
  * they will queue up in the serialization mutex of the futex state container
  * queued in the hash bucket.
+ *
+ * To handle the exceptional case that the futex owner died, the robust
+ * futexes list mechanism is used to for waking up sleeping top waiter.
+ * Checks are also made in the futex_spin_on_owner() loop for dead task
+ * structure or invalid pid. In both cases, the top waiter will take over
+ * the ownership of the futex.
  */
 
 /**
@@ -3537,6 +3562,10 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)
 static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 			       struct futex_state *state)
 {
+#define OWNER_DEAD_MESSAGE					\
+	"futex: owner pid %d of TP futex 0x%lx was %s.\n"	\
+	"\tLock is now acquired by pid %d!\n"
+
 	int ret;
 	u32 uval;
 	u32 owner_pid = 0;
@@ -3551,13 +3580,47 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 			break;
 
 		if ((uval & FUTEX_TID_MASK) != owner_pid) {
-			if (owner_task)
+			if (owner_task) {
+				/*
+				 * task_pi_list_del() should always be
+				 * done before put_task_struct(). The futex
+				 * state may have been dequeued if the task
+				 * is dead.
+				 */
+				if (state->owner) {
+					WARN_ON(state->owner != owner_task);
+					task_pi_list_del(state, true);
+				}
 				put_task_struct(owner_task);
+			}
 
 			owner_pid  = uval & FUTEX_TID_MASK;
 			owner_task = futex_find_get_task(owner_pid);
 		}
 
+		if (unlikely(!owner_task ||
+			    (owner_task->flags & PF_EXITING) ||
+			    (uval & FUTEX_OWNER_DIED))) {
+			/*
+			 * PID invalid or exiting/dead task, we can directly
+			 * grab the lock now.
+			 */
+			u32 curval;
+			char *owner_state;
+
+			ret = cmpxchg_futex_value_locked(&curval, uaddr, uval,
+							 vpid);
+			if (unlikely(ret))
+				break;
+			if (curval != uval)
+				continue;
+			owner_state = (owner_task || (uval & FUTEX_OWNER_DIED))
+				    ? "dead" : "invalid";
+			pr_info(OWNER_DEAD_MESSAGE, owner_pid,
+				(long)uaddr, owner_state, vpid);
+			break;
+		}
+
 		if (need_resched()) {
 			__set_current_state(TASK_RUNNING);
 			schedule_preempt_disabled();
@@ -3576,12 +3639,17 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 
 		/*
 		 * If the owner isn't active, we need to go to sleep after
-		 * making sure that the FUTEX_WAITERS bit is set.
+		 * making sure that the FUTEX_WAITERS bit is set. We also
+		 * need to put the futex state into the futex owner's
+		 * pi_state_list to prevent deadlock when the owner dies.
 		 */
 		ret = futex_set_waiters_bit(uaddr, &uval);
 		if (ret)
 			break;
 
+		if (owner_task && !state->owner)
+			task_pi_list_add(owner_task, state);
+
 		/*
 		 * Do a trylock after setting the task state to make
 		 * sure we won't miss a wakeup.
@@ -3617,8 +3685,13 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 			goto retry;
 	}
 
-	if (owner_task)
+	if (owner_task) {
+		if (state->owner)
+			task_pi_list_del(state, false);
 		put_task_struct(owner_task);
+	} else {
+		WARN_ON(state->owner);
+	}
 
 	/*
 	 * Cleanup futex state.
Signature · 1 line
1.8.3.1
#11 lore raw

PATCH v6 11/22 TP-futex: Implement lock handoff to prevent lock starvation

In reply to Waiman Long

The current TP futexes has no guarantee that the top waiter (serialization mutex owner) can get the lock within a finite time. As a result, lock starvation can happen.

A lock handoff mechanism is added to the TP futexes to prevent lock starvation from happening. The idea is that the top waiter can set a special handoff_pid variable with its own pid. The futex owner will then check this variable at unlock time to see if it should free the futex or change its value to the designated pid to transfer the lock directly to the top waiter.

This change does have the effect of limiting the amount of unfairness and hence may adversely affect performance depending on the workloads.

Currently the handoff mechanism is triggered when the top waiter fails to acquire the lock after about 5ms of spinning and sleeping. So the maximum lock handoff rate is about 200 handoffs per second.

---
 kernel/futex.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 53 insertions(+), 6 deletions(-)
kernel/futex.c +53 -6
index af367e8..b71c411 100644
@@ -63,6 +63,7 @@
 #include <linux/pid.h>
 #include <linux/nsproxy.h>
 #include <linux/ptrace.h>
+#include <linux/sched/clock.h>
 #include <linux/sched/rt.h>
 #include <linux/sched/wake_q.h>
 #include <linux/sched/mm.h>
@@ -237,6 +238,8 @@ struct futex_state {
 	struct task_struct *mutex_owner;	/* For TP futexes only */
 	atomic_t refcount;
 
+	u32 handoff_pid;			/* For TP futexes only */
+
 	enum futex_type type;
 	union futex_key key;
 };
@@ -914,6 +917,7 @@ static int refill_futex_state_cache(void)
 	/* pi_mutex gets initialized later */
 	state->owner = NULL;
 	state->mutex_owner = NULL;
+	state->handoff_pid = 0;
 	atomic_set(&state->refcount, 1);
 	state->key = FUTEX_KEY_INIT;
 
@@ -3349,8 +3353,9 @@ void exit_robust_list(struct task_struct *curr)
  * and unlocking.
  *
  * The purpose of this FUTEX_WAITERS bit is to make the unlocker wake up the
- * serialization mutex owner. Not having the FUTEX_WAITERS bit set doesn't
- * mean there is no waiter in the kernel.
+ * serialization mutex owner or to hand off the lock directly to the top
+ * waiter. Not having the FUTEX_WAITERS bit set doesn't mean there is no
+ * waiter in the kernel.
  *
  * Like PI futexes, TP futexes are orthogonal to robust futexes can be
  * used together.
@@ -3366,6 +3371,16 @@ void exit_robust_list(struct task_struct *curr)
  * the ownership of the futex.
  */
 
+/*
+ * Timeout value for enabling lock handoff and prevent lock starvation.
+ *
+ * Currently, lock handoff will be enabled if the top waiter can't get the
+ * futex after about 5ms. To reduce time checking overhead, it is only done
+ * after every 128 spins or right after wakeup. As a result, the actual
+ * elapsed time will be a bit longer than the specified value.
+ */
+#define TP_HANDOFF_TIMEOUT	5000000	/* 5ms	*/
+
 /**
  * lookup_futex_state - Looking up the futex state structure.
  * @hb:		 hash bucket
@@ -3445,6 +3460,7 @@ static inline int put_futex_state_unlocked(struct futex_state *state)
  * If !steal
  * then
  *   don't preserve the flag bits;
+ *   check for handoff (futex word == own pid)
  * else
  *   preserve the flag bits
  * endif
@@ -3463,6 +3479,9 @@ static inline int __futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,
 
 	uval = *puval;
 
+	if (waiter && (uval & FUTEX_TID_MASK) == vpid)
+		return 1;
+
 	if (uval & FUTEX_TID_MASK)
 		return 0;	/* Trylock fails */
 
@@ -3566,10 +3585,12 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 	"futex: owner pid %d of TP futex 0x%lx was %s.\n"	\
 	"\tLock is now acquired by pid %d!\n"
 
-	int ret;
+	int ret, loopcnt = 1;
+	bool handoff_set = false;
 	u32 uval;
 	u32 owner_pid = 0;
 	struct task_struct *owner_task = NULL;
+	u64 handoff_time = running_clock() + TP_HANDOFF_TIMEOUT;
 
 	preempt_disable();
 	WRITE_ONCE(state->mutex_owner, current);
@@ -3624,6 +3645,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 		if (need_resched()) {
 			__set_current_state(TASK_RUNNING);
 			schedule_preempt_disabled();
+			loopcnt = 0;
 			continue;
 		}
 
@@ -3632,6 +3654,23 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 			break;
 		}
 
+		/*
+		 * Enable lock handoff if the elapsed time exceed the timeout
+		 * value. We also need to set the FUTEX_WAITERS bit to make
+		 * sure that futex lock holder will initiate the handoff at
+		 * unlock time.
+		 */
+		if (!handoff_set && !(loopcnt++ & 0x7f)) {
+			if (running_clock() > handoff_time) {
+				WARN_ON(READ_ONCE(state->handoff_pid));
+				ret = futex_set_waiters_bit(uaddr, &uval);
+				if (ret)
+					break;
+				WRITE_ONCE(state->handoff_pid, vpid);
+				handoff_set = true;
+			}
+		}
+
 		if (owner_task->on_cpu) {
 			cpu_relax();
 			continue;
@@ -3674,8 +3713,10 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 		 * lock stealing happen in between setting the FUTEX_WAITERS
 		 * and setting state to TASK_INTERRUPTIBLE.
 		 */
-		if (!(uval & FUTEX_OWNER_DIED) && (uval & FUTEX_WAITERS))
+		if (!(uval & FUTEX_OWNER_DIED) && (uval & FUTEX_WAITERS)) {
 			schedule_preempt_disabled();
+			loopcnt = 0;
+		}
 		__set_current_state(TASK_RUNNING);
 	}
 
@@ -3697,6 +3738,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 	 * Cleanup futex state.
 	 */
 	WRITE_ONCE(state->mutex_owner, NULL);
+	WRITE_ONCE(state->handoff_pid, 0);
 
 	preempt_enable();
 	return ret;
@@ -3811,6 +3853,7 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)
 static int futex_unlock(u32 __user *uaddr, unsigned int flags)
 {
 	u32 uval, vpid = task_pid_vnr(current);
+	u32 newpid = 0;
 	union futex_key key = FUTEX_KEY_INIT;
 	struct futex_hash_bucket *hb;
 	struct futex_state *state = NULL;
@@ -3844,6 +3887,10 @@ static int futex_unlock(u32 __user *uaddr, unsigned int flags)
 		goto out_put_key;
 	}
 
+	newpid = READ_ONCE(state->handoff_pid);
+	if (newpid)
+		WRITE_ONCE(state->handoff_pid, 0);
+
 	owner = READ_ONCE(state->mutex_owner);
 	if (owner)
 		wake_q_add(&wake_q, owner);
@@ -3851,13 +3898,13 @@ static int futex_unlock(u32 __user *uaddr, unsigned int flags)
 	spin_unlock(&hb->fs_lock);
 
 	/*
-	 * Unlock the futex.
+	 * Unlock the futex or handoff to the next owner.
 	 * The flag bits are not preserved to encourage more lock stealing.
 	 */
 	for (;;) {
 		u32 old = uval;
 
-		if (cmpxchg_futex_value(&uval, uaddr, old, 0)) {
+		if (cmpxchg_futex_value(&uval, uaddr, old, newpid)) {
 			ret = -EFAULT;
 			break;
 		}
Signature · 1 line
1.8.3.1
#12 lore raw

PATCH v6 12/22 TP-futex: Return status code on FUTEX_LOCK calls

In reply to Waiman Long

To better understand how the TP futexes are performing, it is useful to return the internal status on the TP futexes. The FUTEX_LOCK futex(2) syscall will now return a positive status code if no error happens. The status code consists of the following 3 fields:

1) Bits 00-07: code on how the lock is acquired.
2) Bits 08-15: reserved
3) Bits 16-30: how many time the task sleeps in the optimistic spinning loop.

By returning the TP status code, an external monitoring or tracking program can have a macro view of how the TP futexes are performing.

---
 kernel/futex.c | 43 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 32 insertions(+), 11 deletions(-)
kernel/futex.c +32 -11
index b71c411..c0ce1e1 100644
@@ -3381,6 +3381,22 @@ void exit_robust_list(struct task_struct *curr)
  */
 #define TP_HANDOFF_TIMEOUT	5000000	/* 5ms	*/
 
+/*
+ * The futex_lock() function returns the internal status of the TP futex.
+ * The status code consists of the following 3 fields:
+ * 1) bits 00-07: code on how the lock is acquired
+ *		   0 - steals the lock
+ *		   1 - top waiter (mutex owner) acquires the lock
+ *		   2 - handed off the lock
+ * 2) bits 08-15: reserved
+ * 3) bits 15-30: how many times the task has slept or yield to scheduler
+ *		  in futex_spin_on_owner().
+ */
+#define TP_LOCK_STOLEN		0
+#define TP_LOCK_ACQUIRED	1
+#define TP_LOCK_HANDOFF		2
+#define TP_STATUS_SLEEP(val, sleep)	((val)|((sleep) << 16))
+
 /**
  * lookup_futex_state - Looking up the futex state structure.
  * @hb:		 hash bucket
@@ -3465,9 +3481,11 @@ static inline int put_futex_state_unlocked(struct futex_state *state)
  *   preserve the flag bits
  * endif
  *
- * Return: 1 if lock acquired;
+ * Return: TP_LOCK_ACQUIRED if lock acquired;
+ *	   TP_LOCK_HANDOFF if lock was handed off;
  *	   0 if lock acquisition failed;
  *	   -EFAULT if an error happened.
+ *	   *puval will contain the latest futex value when trylock fails.
  */
 static inline int __futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,
 				  const bool steal)
@@ -3479,8 +3497,8 @@ static inline int __futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,
 
 	uval = *puval;
 
-	if (waiter && (uval & FUTEX_TID_MASK) == vpid)
-		return 1;
+	if (!steal && (uval & FUTEX_TID_MASK) == vpid)
+		return TP_LOCK_HANDOFF;
 
 	if (uval & FUTEX_TID_MASK)
 		return 0;	/* Trylock fails */
@@ -3491,7 +3509,7 @@ static inline int __futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,
 	if (unlikely(cmpxchg_futex_value(puval, uaddr, uval, vpid|flags)))
 		return -EFAULT;
 
-	return *puval == uval;
+	return (*puval == uval) ? TP_LOCK_ACQUIRED : 0;
 }
 
 static int futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval)
@@ -3515,7 +3533,8 @@ static int futex_steal_lock(u32 __user *uaddr, const u32 vpid, u32 *puval)
  * of faulting in the futex word. This function should only be called from
  * within futex_spin_on_owner().
  *
- * Return: 1 if lock acquired;
+ * Return: TP_LOCK_ACQUIRED if lock acquired;
+ *	   TP_LOCK_HANDOFF if lock was handed off;
  *	   0 if lock acquisition failed;
  *	   -EFAULT if an error happened.
  */
@@ -3576,7 +3595,7 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)
  * unless the pid wraps around and the perceived owner is not the real owner.
  * To guard against this case, we will have to use the robust futex feature.
  *
- * Return: 0 if futex acquired, < 0 if an error happens.
+ * Return: TP status code if lock acquired, < 0 if an error happens.
  */
 static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 			       struct futex_state *state)
@@ -3586,6 +3605,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 	"\tLock is now acquired by pid %d!\n"
 
 	int ret, loopcnt = 1;
+	int nsleep = 0;
 	bool handoff_set = false;
 	u32 uval;
 	u32 owner_pid = 0;
@@ -3645,6 +3665,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 		if (need_resched()) {
 			__set_current_state(TASK_RUNNING);
 			schedule_preempt_disabled();
+			nsleep++;
 			loopcnt = 0;
 			continue;
 		}
@@ -3715,6 +3736,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 		 */
 		if (!(uval & FUTEX_OWNER_DIED) && (uval & FUTEX_WAITERS)) {
 			schedule_preempt_disabled();
+			nsleep++;
 			loopcnt = 0;
 		}
 		__set_current_state(TASK_RUNNING);
@@ -3741,7 +3763,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 	WRITE_ONCE(state->handoff_pid, 0);
 
 	preempt_enable();
-	return ret;
+	return (ret < 0) ? ret : TP_STATUS_SLEEP(ret, nsleep);
 }
 
 /*
@@ -3755,8 +3777,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
  * This function is not inlined so that it can show up separately in perf
  * profile for performance analysis purpose.
  *
- * Return: 0   - lock acquired
- *	   < 0 - an error happens
+ * Return: TP status code if lock acquired, < 0 if an error happens.
  */
 static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)
 {
@@ -3771,7 +3792,7 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)
 	 */
 	ret = futex_steal_lock(uaddr, vpid, &uval);
 	if (ret)
-		goto out;
+		return (ret < 0) ? ret : TP_LOCK_STOLEN;
 
 	/*
 	 * Detect deadlocks.
@@ -3839,7 +3860,7 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)
 	put_futex_key(&key);
 
 out:
-	return (ret < 0) ? ret : 0;
+	return ret;
 }
 
 /*
Signature · 1 line
1.8.3.1
#13 lore raw

PATCH v6 13/22 TP-futex: Add timeout support

In reply to Waiman Long

Unlike other futexes, TP futexes do not accept the specification of a timeout value. To align with the other futexes, timeout support is now added. Like FUTEX_WAIT, the timeout value is relative to current time, not an absolute time. However, the timeout isn't as precise as the other futex types due to the fact that timer expiration can't be detected when the thread is waiting in the serialization mutex.

---
 kernel/futex.c | 52 +++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 43 insertions(+), 9 deletions(-)
kernel/futex.c +43 -9
index c0ce1e1..b54f429 100644
@@ -3580,9 +3580,10 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)
 
 /**
  * futex_spin_on_owner - Optimistically spin on futex owner
- * @uaddr: futex address
- * @vpid:  PID of current task
- * @state: futex state object
+ * @uaddr:   futex address
+ * @vpid:    PID of current task
+ * @state:   futex state object
+ * @timeout: hrtimer_sleeper structure
  *
  * Spin on the futex word while the futex owner is active. Otherwise, set
  * the FUTEX_WAITERS bit and go to sleep.
@@ -3598,7 +3599,8 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)
  * Return: TP status code if lock acquired, < 0 if an error happens.
  */
 static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
-			       struct futex_state *state)
+			       struct futex_state *state,
+			       struct hrtimer_sleeper *timeout)
 {
 #define OWNER_DEAD_MESSAGE					\
 	"futex: owner pid %d of TP futex 0x%lx was %s.\n"	\
@@ -3670,6 +3672,12 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 			continue;
 		}
 
+
+		if (timeout && !timeout->task) {
+			ret = -ETIMEDOUT;
+			break;
+		}
+
 		if (signal_pending(current)) {
 			ret = -EINTR;
 			break;
@@ -3777,10 +3785,18 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
  * This function is not inlined so that it can show up separately in perf
  * profile for performance analysis purpose.
  *
+ * The timeout functionality isn't as precise as other other futex types
+ * as timer expiration will not be detected while waiting in the
+ * serialization mutex. One possible solution is to modify the expiration
+ * function to send out a signal as well so as to break the thread out of
+ * the mutex code if we really want more precise timeout.
+ *
  * Return: TP status code if lock acquired, < 0 if an error happens.
  */
-static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)
+static noinline int
+futex_lock(u32 __user *uaddr, unsigned int flags, ktime_t *time)
 {
+	struct hrtimer_sleeper timeout, *to;
 	struct futex_hash_bucket *hb;
 	union futex_key key = FUTEX_KEY_INIT;
 	struct futex_state *state;
@@ -3804,6 +3820,8 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)
 	if (refill_futex_state_cache())
 		return -ENOMEM;
 
+	to = futex_setup_timer(time, &timeout, flags, current->timer_slack_ns);
+
 	ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &key, VERIFY_WRITE);
 	if (unlikely(ret))
 		goto out;
@@ -3829,6 +3847,9 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)
 		goto out_put_state_key;
 	}
 
+	if (to)
+		hrtimer_start_expires(&to->timer, HRTIMER_MODE_ABS);
+
 	/*
 	 * Acquiring the serialization mutex.
 	 *
@@ -3840,10 +3861,19 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)
 		goto out_put_state_key;
 
 	/*
+	 * Check for timeout.
+	 */
+	if (to && !timeout.task) {
+		ret = -ETIMEDOUT;
+		mutex_unlock(&state->mutex);
+		goto out_put_state_key;
+	}
+
+	/*
 	 * As the mutex owner, we can now spin on the futex word as well as
 	 * the active-ness of the futex owner.
 	 */
-	ret = futex_spin_on_owner(uaddr, vpid, state);
+	ret = futex_spin_on_owner(uaddr, vpid, state, to);
 
 	mutex_unlock(&state->mutex);
 
@@ -3860,6 +3890,10 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)
 	put_futex_key(&key);
 
 out:
+	if (to) {
+		hrtimer_cancel(&to->timer);
+		destroy_hrtimer_on_stack(&to->timer);
+	}
 	return ret;
 }
 
@@ -4013,7 +4047,7 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
 		return futex_requeue(uaddr, flags, uaddr2, val, val2, &val3, 1);
 #ifdef CONFIG_SMP
 	case FUTEX_LOCK:
-		return futex_lock(uaddr, flags);
+		return futex_lock(uaddr, flags, timeout);
 	case FUTEX_UNLOCK:
 		return futex_unlock(uaddr, flags);
 #endif
@@ -4032,7 +4066,7 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
 	int cmd = op & FUTEX_CMD_MASK;
 
 	if (utime && (cmd == FUTEX_WAIT || cmd == FUTEX_LOCK_PI ||
-		      cmd == FUTEX_WAIT_BITSET ||
+		      cmd == FUTEX_WAIT_BITSET || cmd == FUTEX_LOCK ||
 		      cmd == FUTEX_WAIT_REQUEUE_PI)) {
 		if (unlikely(should_fail_futex(!(op & FUTEX_PRIVATE_FLAG))))
 			return -EFAULT;
@@ -4042,7 +4076,7 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
 			return -EINVAL;
 
 		t = timespec_to_ktime(ts);
-		if (cmd == FUTEX_WAIT)
+		if (cmd == FUTEX_WAIT || cmd == FUTEX_LOCK)
 			t = ktime_add_safe(ktime_get(), t);
 		tp = &t;
 	}
Signature · 1 line
1.8.3.1
#14 lore raw

PATCH v6 14/22 TP-futex: Optionally return EAGAIN for userspace locking

In reply to Waiman Long

A performance disadvantage of TP futexes versus wait-wake futex is the fact that locking was done in the kernel for TP futexes instead of in the userspace like the WW futexes. The additional latency between when the lock was taken in the kernel and when the task returned to the userspace would be added to the lock hold time. This could make TP futexes less performant than WW futexes in some circumstances.

To remedy this deficiency, we now allow users to specify where locking should be done.

  futex(uaddr, FUTEX_LOCK, uslock, timeout, NULL, 0);

If the uslock flag is set, the EAGAIN error will be returned like the WW futexes to indicate that locking can now be done in the userspace. If it is not set, locking will be done in the kernel instead. It is possible that lock handoff can happen even if uslock flag is set. So the userspace code must check the lock value to see if this is the case.

Doing locking in the userspace can lead to lock starvation in some cases unless some precautionary measure is taken. So it is recommended that kernel locking should be performed after a number of failures in userspace locking.

---
 kernel/futex.c | 53 +++++++++++++++++++++++++++++++++++------------------
 1 file changed, 35 insertions(+), 18 deletions(-)
kernel/futex.c +35 -18
index b54f429..7f41999 100644
@@ -196,6 +196,7 @@
 #endif
 #define FLAGS_CLOCKRT		0x02
 #define FLAGS_HAS_TIMEOUT	0x04
+#define FLAGS_TP_USLOCK		0x08	/* Do the locking in userspace */
 
 enum futex_type {
 	TYPE_PI = 0,
@@ -3465,10 +3466,11 @@ static inline int put_futex_state_unlocked(struct futex_state *state)
 
 /**
  * __futex_trylock - try to lock the userspace futex word (0 => vpid).
- * @uaddr: futex address
- * @vpid:  PID of current task
- * @puval: storage location for current futex value
- * @steal: false for top waiter, true otherwise for lock stealing
+ * @uaddr:   futex address
+ * @vpid:    PID of current task
+ * @puval:   storage location for current futex value
+ * @steal:   false for top waiter, true otherwise for lock stealing
+ * @chkonly: check lock status without actual locking
  *
  * The HB fs_lock should NOT be held while calling this function.
  * The flag bits are ignored in the trylock.
@@ -3485,10 +3487,11 @@ static inline int put_futex_state_unlocked(struct futex_state *state)
  *	   TP_LOCK_HANDOFF if lock was handed off;
  *	   0 if lock acquisition failed;
  *	   -EFAULT if an error happened.
+ *	   -EAGAIN if chkonly and lock is free
  *	   *puval will contain the latest futex value when trylock fails.
  */
 static inline int __futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,
-				  const bool steal)
+				  const bool steal, bool chkonly)
 {
 	u32 uval, flags = 0;
 
@@ -3500,6 +3503,9 @@ static inline int __futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,
 	if (!steal && (uval & FUTEX_TID_MASK) == vpid)
 		return TP_LOCK_HANDOFF;
 
+	if (chkonly && !uval)
+		return -EAGAIN;
+
 	if (uval & FUTEX_TID_MASK)
 		return 0;	/* Trylock fails */
 
@@ -3512,21 +3518,23 @@ static inline int __futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,
 	return (*puval == uval) ? TP_LOCK_ACQUIRED : 0;
 }
 
-static int futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval)
+static int futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,
+			 bool chkonly)
 {
-	return __futex_trylock(uaddr, vpid, puval, false);
+	return __futex_trylock(uaddr, vpid, puval, false, chkonly);
 }
 
 static int futex_steal_lock(u32 __user *uaddr, const u32 vpid, u32 *puval)
 {
-	return __futex_trylock(uaddr, vpid, puval, true);
+	return __futex_trylock(uaddr, vpid, puval, true, false);
 }
 
 /**
  * futex_trylock_preempt_disabled - futex_trylock with preemption disabled
- * @uaddr: futex address
- * @vpid:  PID of current task
- * @puval: storage location for current futex value
+ * @uaddr:   futex address
+ * @vpid:    PID of current task
+ * @puval:   storage location for current futex value
+ * @chkonly: check lock status without actual locking
  *
  * The preempt_disable() has similar effect as pagefault_disable(). As a
  * result, we will have to disable page fault as well and handle the case
@@ -3537,14 +3545,15 @@ static int futex_steal_lock(u32 __user *uaddr, const u32 vpid, u32 *puval)
  *	   TP_LOCK_HANDOFF if lock was handed off;
  *	   0 if lock acquisition failed;
  *	   -EFAULT if an error happened.
+ *	   -EAGAIN if chkonly and lock is free
  */
 static inline int futex_trylock_preempt_disabled(u32 __user *uaddr,
-						 const u32 vpid, u32 *puval)
+			const u32 vpid, u32 *puval, bool chkonly)
 {
 	int ret;
 
 	pagefault_disable();
-	ret = futex_trylock(uaddr, vpid, puval);
+	ret = futex_trylock(uaddr, vpid, puval, chkonly);
 	pagefault_enable();
 
 	return ret;
@@ -3584,6 +3593,7 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)
  * @vpid:    PID of current task
  * @state:   futex state object
  * @timeout: hrtimer_sleeper structure
+ * @chkonly: check lock status without actual locking
  *
  * Spin on the futex word while the futex owner is active. Otherwise, set
  * the FUTEX_WAITERS bit and go to sleep.
@@ -3600,7 +3610,8 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)
  */
 static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 			       struct futex_state *state,
-			       struct hrtimer_sleeper *timeout)
+			       struct hrtimer_sleeper *timeout,
+			       bool chkonly)
 {
 #define OWNER_DEAD_MESSAGE					\
 	"futex: owner pid %d of TP futex 0x%lx was %s.\n"	\
@@ -3618,7 +3629,8 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 	WRITE_ONCE(state->mutex_owner, current);
 retry:
 	for (;;) {
-		ret = futex_trylock_preempt_disabled(uaddr, vpid, &uval);
+		ret = futex_trylock_preempt_disabled(uaddr, vpid, &uval,
+						     chkonly);
 		if (ret)
 			break;
 
@@ -3730,7 +3742,8 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 		 * wakeup		sleep
 		 */
 		set_current_state(TASK_INTERRUPTIBLE);
-		ret = futex_trylock_preempt_disabled(uaddr, vpid, &uval);
+		ret = futex_trylock_preempt_disabled(uaddr, vpid, &uval,
+						     chkonly);
 		if (ret) {
 			__set_current_state(TASK_RUNNING);
 			break;
@@ -3871,9 +3884,11 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 
 	/*
 	 * As the mutex owner, we can now spin on the futex word as well as
-	 * the active-ness of the futex owner.
+	 * the active-ness of the futex owner. The spinner will return EAGAIN
+	 * without taking the lock when the userspace locking flag is set.
 	 */
-	ret = futex_spin_on_owner(uaddr, vpid, state, to);
+	ret = futex_spin_on_owner(uaddr, vpid, state, to,
+				  flags & FLAGS_TP_USLOCK);
 
 	mutex_unlock(&state->mutex);
 
@@ -4047,6 +4062,8 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
 		return futex_requeue(uaddr, flags, uaddr2, val, val2, &val3, 1);
 #ifdef CONFIG_SMP
 	case FUTEX_LOCK:
+		if (val)
+			flags |= FLAGS_TP_USLOCK;
 		return futex_lock(uaddr, flags, timeout);
 	case FUTEX_UNLOCK:
 		return futex_unlock(uaddr, flags);
Signature · 1 line
1.8.3.1
#15 lore raw

PATCH v6 15/22 TP-futex, doc: Add TP futexes documentation

In reply to Waiman Long

This patch adds a new document file on how to use the TP futexes.

In term of locking performance, a futex locking microbenchmark was written where separate userspace mutexes are implemented using wait-wake (WW), PI and TP futexes respectively. This microbenchmark was intructed to run 10s of locking operations with a load-to-load latency of 10 on a 2-socket 36-core E5-2699 v3 system (HT off). The added latency was there to reduce the chance that a given CPU could monopolize the lock over multiple consecutive locking operations. The system was running on a 4.11 based kernel. the results of the benchmark runs were as follows:

                      WW futex    PI futex   TP futex     Glibc
                      --------    --------   --------     -----
Total locking ops    49,065,154   372,301   57,209,013  39,667,389
Per-thread avg/sec      136,291     1,034      158,897     110,186
Per-thread min/sec      131,597     1,033      139,441     106,397
Per-thread max/sec      140,414     1,051      176,816     117,527
% Stddev                  0.22%     0.05%        1.27%       0.36%
lock slowpaths       10,292,953   372,114   11,864,228       -
unlock slowpaths     14,306,857   372,110           25       -

Besides a 17% performance increase, another major difference between WW and TP futexes was the dramatic reduction in the number of unlock slowpaths that were being taken.

By increasing the load latency (amount of time spent in the critical section) from a default of 1, the performance discrepancies between WW and TP futexes actually increases in the x86 system as shown in the tables below.

  Load Latency  WW locking ops  TP locking ops  % change
  ------------  --------------  --------------  --------
      5           40,365,507      54,993,009      +36%
     10           33,193,154      47,696,618      +44%
     20           25,855,039      37,553,477      +45%
     30           21,157,759      31,667,873      +50%
     40           18,216,561      27,408,570      +50%
     50           16,103,078      24,299,436      +51%
    100           10,233,914      14,375,505      +40%

1us sleep 178,018 174,938 -2%

The performance advantage increased to about 50% and then started dropping off. The TP futexes, however, are not designed for sleeping lock holders as there are no performance advantage.

On a 2-socket 10-core and 80-thread Power8 system, the benchmark results were:

                      WW futex    PI futex   TP futex     Glibc
                      --------    --------   --------     -----
Total locking ops    12,939,135  1,758,607  21,502,680  10,896,395
Per-thread avg/sec       16,172      2,198      26,851      13,619
Per-thread min/sec       13,527      2,197      23,101      11,524
Per-thread max/sec       19,519      2,220      27,629      15,952
% Stddev                  0.93%      0.01%       0.25%       0.87%
lock slowpaths        2,856,730  1,758,403   7,092,852       -
unlock slowpaths      4,279,445  1,758,403          13       -
---
 Documentation/00-INDEX     |   2 +
 Documentation/tp-futex.txt | 180 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 182 insertions(+)
 create mode 100644 Documentation/tp-futex.txt
Documentation/00-INDEX +2 -0
index 793acf9..436d2ed 100644
@@ -416,6 +416,8 @@ this_cpu_ops.txt
 	- List rationale behind and the way to use this_cpu operations.
 thermal/
 	- directory with information on managing thermal issues (CPU/temp)
+tp-futex.txt
+	- Documentation on lightweight throughput-optimized futexes.
 trace/
 	- directory with info on tracing technologies within linux
 translations/
Documentation/tp-futex.txt new file +180 -0

180 lines not on this page · raw message

Signature · 1 line
1.8.3.1
#16 lore raw

PATCH v6 16/22 TP-futex: Support userspace reader/writer locks

In reply to Waiman Long

The TP futexes was designed to support userspace mutually exclusive locks. They are now extended to support userspace reader/writer locks as well.

Two new futex command codes are added:
1) FUTEX_LOCK_SHARED - to acquire a shared lock (reader-lock)
2) FUTEX_UNLOCK_SHARED - to release a shred lock (reader-unlock)

The original FUTEX_LOCK and FUTEX_UNLOCK command codes acts like a writer lock and unlock method in a userspace rwlock.

A special bit FUTEX_SHARED (bit 29) is used to indicate if a lock is an exclusive (writer) or shared (reader) lock. Because of that, the maximum TID value is now reduce to 1^29 instead of 1^30. With a shared lock, the lower 24 bits are used to track the reader counts. Bit 28 is a special FUTEX_SHARED_UNLOCK bit that should used by userspace to prevent racing in the FUTEX_UNLOCK_SHARED futex(2) syscall. Bits 24-27 are reserved for future extension.

When a TP futex becomes reader-owned, there is no way to tell if the readers are running or not. So a fixed time interval (50us) is now used for spinning on a reader-owned futex. The top waiter will abort the spinning and goes to sleep when no activity is observed in the futex value itself after the spinning timer expired. Activities here means any change in the reader count of the futex indicating that some new readers are coming in or old readers are leaving.

Sample code to acquire/release a reader lock in userspace are:

  void read_lock(int *faddr)
  {
  	int val = cmpxchg(faddr, 0, FUTEX_SHARED + 1);
	if (!val)
		return;
	for (;;) {
		int old = val, new;

		if (!old)
			new = FUTEX_SHARED + 1;
		else if ((old & (~FUTEX_TID_SHARED_MASK|
				  FUTEX_SHARED_UNLOCK) ||
			!(old & FUTEX_SHARED))
			break;
		else
			new = old + 1;
		val = cmpxchg(faddr, old, new);
		if (val == old)
			return;
	}
	while (futex(faddr, FUTEX_LOCK_SHARED, ...) < 0)
		;

}

void read_unlock(int *faddr)

  {
	int val = xadd(faddr, -1);
	int old;

	for (;;) {
		if ((val & (FUTEX_SCNT_MASK|FUTEX_SHARED_UNLOCK) ||
		   !(val & FUTEX_SHARED))
			return;	/* Not the last reader or shared */
		if (val & ~FUTEX_SHARED_TID_MASK) {
			old = cmpxchg(faddr, val, val|FUTEX_SHARED_UNLOCK);
			if (old == val)
				break;
		} else {
	    		old = cmpxchg(faddr, val, 0);
			if (old == val)
				return;
		}
		val = old;
	}
	futex(faddr, FUTEX_UNLOCK_SHARED, ...);

}

The read lock/unlock code is a bit more complicated than the corresponding write lock/unlock code. This is by design to minimize any additional overhead for mutex lock and unlock code.

---
 include/uapi/linux/futex.h |  28 +++++-
 kernel/futex.c             | 228 ++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 221 insertions(+), 35 deletions(-)
include/uapi/linux/futex.h +26 -2
index 7f676ac..1a22ecc 100644
@@ -22,6 +22,8 @@
 #define FUTEX_CMP_REQUEUE_PI	12
 #define FUTEX_LOCK		13
 #define FUTEX_UNLOCK		14
+#define FUTEX_LOCK_SHARED	15
+#define FUTEX_UNLOCK_SHARED	16
 
 #define FUTEX_PRIVATE_FLAG	128
 #define FUTEX_CLOCK_REALTIME	256
@@ -43,6 +45,9 @@
 					 FUTEX_PRIVATE_FLAG)
 #define FUTEX_LOCK_PRIVATE	(FUTEX_LOCK | FUTEX_PRIVATE_FLAG)
 #define FUTEX_UNLOCK_PRIVATE	(FUTEX_UNLOCK | FUTEX_PRIVATE_FLAG)
+#define FUTEX_LOCK_SHARED_PRIVATE	(FUTEX_LOCK_SHARED | FUTEX_PRIVATE_FLAG)
+#define FUTEX_UNLOCK_SHARED_PRIVATE	(FUTEX_UNLOCK_SHARED | \
+					 FUTEX_PRIVATE_FLAG)
 
 /*
  * Support for robust futexes: the kernel cleans up held futexes at
@@ -111,9 +116,28 @@ struct robust_list_head {
 #define FUTEX_OWNER_DIED	0x40000000
 
 /*
- * The rest of the robust-futex field is for the TID:
+ * Shared locking bit (for readers).
+ * Robust futexes cannot be used in combination with shared locking.
+ *
+ * The FUTEX_SHARED_UNLOCK bit can be set by userspace to indicate that
+ * an shared unlock is in progress and so no shared locking is allowed
+ * within the kernel.
+ */
+#define FUTEX_SHARED		0x20000000
+#define FUTEX_SHARED_UNLOCK	0x10000000
+
+/*
+ * The rest of the bits is for the TID or, in the case of readers, the
+ * shared locking bit and the numbers of active readers present. IOW, the
+ * max supported TID is actually 0x1fffffff. Only 24 bits are used for
+ * tracking the number of shared futex owners. Bits 24-27 may be used by
+ * the userspace for internal management purpose. However, a successful
+ * FUTEX_UNLOCK_SHARED futex(2) syscall will clear or alter those bits.
  */
-#define FUTEX_TID_MASK		0x3fffffff
+#define FUTEX_TID_MASK		0x1fffffff
+#define FUTEX_SCNT_MASK		0x00ffffff	/* Shared futex owner count */
+#define FUTEX_SHARED_TID_MASK	0x3fffffff	/* FUTEX_SHARED bit + TID   */
+#define FUTEX_SHARED_SCNT_MASK	0x20ffffff	/* FUTEX_SHARED bit + count */
 
 /*
  * This limit protects against a deliberately circular list.
kernel/futex.c +195 -33

419 lines not on this page · raw message

Signature · 1 line
1.8.3.1
#17 lore raw

PATCH v6 17/22 TP-futex: Enable kernel reader lock stealing

In reply to Waiman Long

By default, the TP futexes do not have preference for either readers or writers. Most reader-writer locks allows users to decide if they want to prefer readers or writers more.

This patch allows the setting of the prefer-reader mode in the val argument of the futex system call. If that flag is set, it will enable kernel reader to steal the lock when the futex is currently reader-owned and the lock handoff mechanism hasn't been enabled yet.

---
 kernel/futex.c | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)
kernel/futex.c +18 -2
index 4139843..cacaaf1 100644
@@ -197,6 +197,7 @@
 #define FLAGS_CLOCKRT		0x02
 #define FLAGS_HAS_TIMEOUT	0x04
 #define FLAGS_TP_USLOCK		0x08	/* Do the locking in userspace */
+#define FLAGS_TP_PREADER	0x08	/* Prefer readers */
 
 enum futex_type {
 	TYPE_PI = 0,
@@ -4004,6 +4005,20 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags,
 		goto out_put_state_key;
 	}
 
+	/*
+	 * For reader, we will try to steal the lock here as if it is the
+	 * top waiter without taking the serialization mutex if the handoff
+	 * PID hasn't been set and is in prefer-reader mode.
+	 */
+	if (shared && (flags & FLAGS_TP_PREADER) && !state->handoff_pid) {
+		ret = futex_trylock(uaddr, vpid, &uval, true);
+		if (ret) {
+			if (ret > 0)
+				ret = TP_LOCK_STOLEN;
+			goto out_put_state_key;
+		}
+	}
+
 	if (to)
 		hrtimer_start_expires(&to->timer, HRTIMER_MODE_ABS);
 
@@ -4219,8 +4234,9 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
 #ifdef CONFIG_SMP
 	case FUTEX_LOCK:
 	case FUTEX_LOCK_SHARED:
-		if (val && (cmd == FUTEX_LOCK))
-			flags |= FLAGS_TP_USLOCK;
+		if (val)
+			flags |= (cmd == FUTEX_LOCK) ? FLAGS_TP_USLOCK
+						     : FLAGS_TP_PREADER;
 		return futex_lock(uaddr, flags, timeout,
 				 (cmd == FUTEX_LOCK) ? false : true);
 	case FUTEX_UNLOCK:
Signature · 1 line
1.8.3.1
#18 lore raw

PATCH v6 18/22 TP-futex: Group readers together in wait queue

In reply to Waiman Long

All the TP futex lock waiters are serialized in the kernel using a kernel mutex which acts like a wait queue. The order at which the waiters popped out from the wait queue will affect performance when exclusive (writer) and shared (reader) lock waiters are mixed in the queue. The worst case scenarios will be something like RWRWRW... in term of ordering where no parallelism in term of lock ownership can happen.

To improve throughput, the readers are now grouped together as a single entity in the wait queue. The first reader that enters the mutex wait queue will become the leader of the group. The other readers will spin on the group leader via an OSQ lock. When the futex is put in the shared mode, either by the group leader or by an external reader the spinning readers in the reader group will then acquire the read lock successively.

The spinning readers in the group will get disbanded when the group leader goes to sleep. In this case, all those readers will go into the mutex wait queue alone and wait for their turn to acquire the TP futex.

---
 kernel/futex.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 140 insertions(+), 3 deletions(-)
kernel/futex.c +140 -3
index cacaaf1..984b836 100644
@@ -71,6 +71,7 @@
 #include <linux/freezer.h>
 #include <linux/bootmem.h>
 #include <linux/fault-inject.h>
+#include <linux/osq_lock.h>
 
 #include <asm/futex.h>
 
@@ -242,6 +243,16 @@ struct futex_state {
 
 	u32 handoff_pid;			/* For TP futexes only */
 
+	/*
+	 * To improve reader throughput in TP futexes, all the readers
+	 * in the mutex queue are grouped together. The first reader in the
+	 * queue will set first_reader, then the rest of the readers will
+	 * spin on the first reader via the OSQ without actually entering
+	 * the mutex queue.
+	 */
+	struct optimistic_spin_queue reader_osq;
+	struct task_struct *first_reader;
+
 	enum futex_type type;
 	union futex_key key;
 };
@@ -3403,14 +3414,21 @@ void exit_robust_list(struct task_struct *curr)
  *		   0 - steals the lock
  *		   1 - top waiter (mutex owner) acquires the lock
  *		   2 - handed off the lock
- * 2) bits 08-15: reserved
- * 3) bits 15-30: how many times the task has slept or yield to scheduler
+ * 2) bit  08:	  1 if reader spins alone		(shared lock only)
+ *    bit  09:	  1 if reader is a spin group leader	(shared lock only)
+ *    bits 10-16: reserved
+ * 3) bits 16-30: how many times the task has slept or yield to scheduler
  *		  in futex_spin_on_owner().
  */
 #define TP_LOCK_STOLEN		0
 #define TP_LOCK_ACQUIRED	1
 #define TP_LOCK_HANDOFF		2
+
+#define TP_READER_ALONE		1
+#define TP_READER_GROUP		2
+
 #define TP_STATUS_SLEEP(val, sleep)	((val)|((sleep) << 16))
+#define TP_STATUS_ALONE(val, alone)	((val)|((alone) << 8))
 
 /**
  * lookup_futex_state - Looking up the futex state structure.
@@ -3452,6 +3470,7 @@ void exit_robust_list(struct task_struct *curr)
 	state = alloc_futex_state();
 	state->type = TYPE_TP;
 	state->key = *key;
+	osq_lock_init(&state->reader_osq);
 	list_add(&state->fs_list, &hb->fs_head);
 	WARN_ON(atomic_read(&state->refcount) != 1);
 
@@ -3930,6 +3949,105 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,
 	return (ret < 0) ? ret : TP_STATUS_SLEEP(ret, nsleep);
 }
 
+/**
+ * futex_spin_on_reader - Optimistically spin on first reader
+ * @uaddr:	   futex address
+ * @pfirst:	   pointer to first reader
+ * @state:	   futex state object
+ * @timeout:	   hrtimer_sleeper structure
+ * @prefer_reader: prefer reader if set
+ *
+ * Reader performance will depend on the placement of readers within the
+ * mutex queue. For a queue of 4 readers and 4 writers, for example, the
+ * optimal placement will be either RRRRWWWW or WWWWRRRR. A worse case will
+ * be RWRWRWRW.
+ *
+ * One way to avoid the worst case scenario is to gather all the readers
+ * together as a single unit and place it into the mutex queue. This is done
+ * by having the first reader puts its task structure into state->first_reader
+ * and the rest of the readers optimistically spin on it instead of entering
+ * the mutex queue.
+ *
+ * On prefer-reader mode, the OSQ lock holder will attempt to grab the shared
+ * lock irrespective of the position of the first reader. Otherwise, it will
+ * only grab the lock when the first reader is the serializaton mutex owner.
+ *
+ * On exit from this function, the reader would have
+ *  1) acquired a read lock on the futex;
+ *  2) become the first reader and goes into the mutex queue; or
+ *  3) seen the first reader slept and needs to go into the mutex queue alone.
+ *
+ * Any fault on accessing the futex will cause it to return 0 and goes into
+ * the mutex queue.
+ *
+ * Return:  > 0 - acquired the read lock
+ *	   <= 0 - need to goes into the mutex queue
+ */
+static int futex_spin_on_reader(u32 __user *uaddr, struct task_struct **pfirst,
+				struct futex_state *state,
+				struct hrtimer_sleeper *timeout,
+				u32 prefer_reader)
+{
+	struct task_struct *first_reader;
+	int ret = 0;
+
+	preempt_disable();
+
+retry:
+	first_reader = READ_ONCE(state->first_reader);
+	if (!first_reader)
+		first_reader = cmpxchg(&state->first_reader, NULL, current);
+	if (!first_reader)
+		goto out;	/* Became the first reader */
+
+	if (!osq_lock(&state->reader_osq))
+		goto reschedule;
+
+	rcu_read_lock();
+	for (;;) {
+		u32 uval;
+
+		if (!state->handoff_pid && (prefer_reader ||
+		   (first_reader == READ_ONCE(state->mutex_owner)))) {
+			ret = futex_trylock_preempt_disabled(uaddr,
+						FUTEX_SHARED, &uval, false);
+			/*
+			 * Return if lock acquired or an error happened
+			 */
+			if (ret)
+				break;
+		}
+
+		/*
+		 * Reread the first reader value again.
+		 */
+		first_reader = READ_ONCE(state->first_reader);
+		if (!first_reader)
+			first_reader = cmpxchg(&state->first_reader, NULL,
+						current);
+		if (!first_reader || !first_reader->on_cpu)
+			break;
+
+		if (need_resched()) {
+			rcu_read_unlock();
+			osq_unlock(&state->reader_osq);
+			goto reschedule;
+		}
+
+		cpu_relax();
+	}
+	rcu_read_unlock();
+	osq_unlock(&state->reader_osq);
+out:
+	*pfirst = first_reader;
+	preempt_enable();
+	return ret;
+
+reschedule:
+	schedule_preempt_disabled();
+	goto retry;
+}
+
 /*
  * Userspace tried a 0 -> TID atomic transition of the futex value
  * and failed. The kernel side here does the whole locking operation.
@@ -3956,9 +4074,11 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags,
 {
 	struct hrtimer_sleeper timeout, *to;
 	struct futex_hash_bucket *hb;
+	struct task_struct *first_reader = NULL;
 	union futex_key key = FUTEX_KEY_INIT;
 	struct futex_state *state;
 	u32 uval, vpid = shared ? FUTEX_SHARED : task_pid_vnr(current);
+	int alone = 0;
 	int ret;
 
 	/*
@@ -4023,6 +4143,17 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags,
 		hrtimer_start_expires(&to->timer, HRTIMER_MODE_ABS);
 
 	/*
+	 * Spin on the first reader and return if we acquired the read lock.
+	 */
+	if (shared) {
+		int rspin_ret = futex_spin_on_reader(uaddr, &first_reader,
+					state, to, flags & FLAGS_TP_PREADER);
+		if (rspin_ret > 0)
+			goto out_put_state_key;
+		alone = first_reader ? TP_READER_ALONE : TP_READER_GROUP;
+	}
+
+	/*
 	 * Acquiring the serialization mutex.
 	 *
 	 * If we got a signal or has some other error, we need to abort
@@ -4052,6 +4183,12 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags,
 	mutex_unlock(&state->mutex);
 
 out_put_state_key:
+	/*
+	 * We will be the first reader if (first_reader == NULL).
+	 */
+	if (shared && !first_reader)
+		WRITE_ONCE(state->first_reader, NULL);
+
 	if (!put_futex_state_unlocked(state)) {
 		/*
 		 * May need to free the futex state object and so must be
@@ -4068,7 +4205,7 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags,
 		hrtimer_cancel(&to->timer);
 		destroy_hrtimer_on_stack(&to->timer);
 	}
-	return ret;
+	return (ret < 0) ? ret : TP_STATUS_ALONE(ret, alone);
 }
 
 /*
Signature · 1 line
1.8.3.1
#19 lore raw

Re: [PATCH-tip v6 18/22] TP-futex: Group readers together in wait queue

url: https://github.com/0day-ci/linux/commits/Waiman-Long/perf-bench-New-microbenchmark-for-userspace-mutex-performance/20170324-135043 config: m32r-usrv_defconfig (attached as .config) compiler…

Hi Waiman,

[auto build test ERROR on next-20170323]
[also build test ERROR on v4.11-rc3]
[cannot apply to linus/master linux/master tip/perf/core v4.9-rc8 v4.9-rc7 v4.9-rc6] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url: https://github.com/0day-ci/linux/commits/Waiman-Long/perf-bench-New-microbenchmark-for-userspace-mutex-performance/20170324-135043
config: m32r-usrv_defconfig (attached as .config)
compiler: m32r-linux-gcc (GCC) 6.2.0
reproduce:
wget https://raw.githubusercontent.com/01org/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=m32r

All errors (new ones prefixed by >>):

kernel/built-in.o: In function `futex_spin_on_reader':

>> kernel/futex.c:4003: undefined reference to `osq_lock'
   kernel/futex.c:4003:(.text+0x5cc98): relocation truncated to fit: R_M32R_26_PCREL_RELA against undefined symbol `osq_lock'
>> kernel/futex.c:4040: undefined reference to `osq_unlock'

kernel/futex.c:4040:(.text+0x5cd2c): relocation truncated to fit: R_M32R_26_PCREL_RELA against undefined symbol osq_unlock' kernel/futex.c:4033: undefined reference to osq_unlock'
kernel/futex.c:4033:(.text+0x5d0d4): relocation truncated to fit: R_M32R_26_PCREL_RELA against undefined symbol `osq_unlock'

vim +4003 kernel/futex.c

3997 first_reader = READ_ONCE(state->first_reader);
3998 if (!first_reader)
3999 first_reader = cmpxchg(&state->first_reader, NULL, current);
4000 if (!first_reader)
4001 goto out; /* Became the first reader */
4002

> 4003		if (!osq_lock(&state->reader_osq))
  4004			goto reschedule;
  4005	
  4006		rcu_read_lock();
  4007		for (;;) {
  4008			u32 uval;
  4009	
  4010			if (!state->handoff_pid && (prefer_reader ||
  4011			   (first_reader == READ_ONCE(state->mutex_owner)))) {
  4012				ret = futex_trylock_preempt_disabled(uaddr,
  4013							FUTEX_SHARED, &uval, false);
  4014				/*
  4015				 * Return if lock acquired or an error happened
  4016				 */
  4017				if (ret)
  4018					break;
  4019			}
  4020	
  4021			/*
  4022			 * Reread the first reader value again.
  4023			 */
  4024			first_reader = READ_ONCE(state->first_reader);
  4025			if (!first_reader)
  4026				first_reader = cmpxchg(&state->first_reader, NULL,
  4027							current);
  4028			if (!first_reader || !first_reader->on_cpu)
  4029				break;
  4030	
  4031			if (need_resched()) {
  4032				rcu_read_unlock();
  4033				osq_unlock(&state->reader_osq);
  4034				goto reschedule;
  4035			}
  4036	
  4037			cpu_relax();
  4038		}
  4039		rcu_read_unlock();
> 4040		osq_unlock(&state->reader_osq);

4041 out:
4042 *pfirst = first_reader;
4043 preempt_enable();

---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation

#20 lore raw

Re: [PATCH-tip v6 18/22] TP-futex: Group readers together in wait queue

In reply to Waiman Long
url: https://github.com/0day-ci/linux/commits/Waiman-Long/perf-bench-New-microbenchmark-for-userspace-mutex-performance/20170324-135043 config: ia64-defconfig (attached as .config) compiler…

Hi Waiman,

[auto build test ERROR on next-20170323]
[also build test ERROR on v4.11-rc3]
[cannot apply to linus/master linux/master tip/perf/core v4.9-rc8 v4.9-rc7 v4.9-rc6] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url: https://github.com/0day-ci/linux/commits/Waiman-Long/perf-bench-New-microbenchmark-for-userspace-mutex-performance/20170324-135043
config: ia64-defconfig (attached as .config)
compiler: ia64-linux-gcc (GCC) 6.2.0
reproduce:
wget https://raw.githubusercontent.com/01org/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=ia64

All errors (new ones prefixed by >>):

kernel/built-in.o: In function `futex_lock':

>> futex.c:(.text+0xfa4a2): undefined reference to `osq_lock'
>> futex.c:(.text+0xfae22): undefined reference to `osq_unlock'

futex.c:(.text+0xfaf02): undefined reference to osq_unlock' futex.c:(.text+0xfb502): undefined reference to osq_lock' futex.c:(.text+0xfb9e2): undefined reference to `osq_unlock'

---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation

#21 lore raw

PATCH v6 19/22 TP-futex, doc: Update TP futexes document on shared locking

In reply to Waiman Long

The tp-futex.txt was updated to add description about shared locking support.

Running the futex locking microbenchmark on a 2-socket 36-core E5-2699 v3 system (HT off) running on a 4.11 based kernel, the performance of TP rwlock with 1:1 reader/writer ratio versus one based on the wait-wake futexes as well as the Glibc rwlock with a microbenchmark (1 worker thread per cpu core) running for 10s with a load-to-load latency of 10 were as follows:

                          WW futex       TP futex         Glibc
                          --------       --------         -----
Total locking ops        30,992,772     49,786,118     14,856,400
Per-thread avg/sec           86,004        138,280         41,267
Per-thread min/sec           81,158        126,338         40,930
Per-thread max/sec           93,940        158,781         41,929
Write lock slowpaths      6,806,849      7,717,125          -
Write unlock slowpaths    8,996,339              5          -
Read lock slowpaths       2,317,716      3,883,809          -
Read unlock slowpaths     4,325,494              2          -

The throughput of TP futex as a read/write lock was about 1.6X of the WW futex and was about 3.4X of glibc.

The following tables shows the average per-thread locking rates (36 threads) with different reader percentages:

                          WW futex       TP futex       Glibc
                          --------       --------       -----
   90% readers            112,868        113,925        62,321
   95% readers            132,809        110,293        67,664
  100% reader             152,015        167,902        79,087

With separate 18 reader and 18 writer threads, the the average per-thread reader and writer locking rates with different load latencies (l) and reader-preferring rwlocks were:

                          WW futex       TP futex       Glibc
                          --------       --------       -----
 Reader rate, l=1         330,787        278,192       172,245

Writer rate, l=1 0 13,804 0

Reader rate, l=5 292,609 267,004 161,246
Writer rate, l=5 0 14,995 0

Reader rate, l=50 324,042 251,083 131,786
Writer rate, l=50 0 4,658 0

The corresponding locking rates with writer-preferring rwlocks are:

                          WW futex       TP futex       Glibc
                          --------       --------       -----
 Reader rate, L=1         118,959        276,180            68

Writer rate, L=1 24,966 24,739 75,969

Reader rate, L=5 123,325 280,492 81
Writer rate, L=5 28,328 21,874 82,331

Reader rate, L=50 88,673 280,288 4
Writer rate, L=50 20,442 5,816 78,522

With both the WW futex and Glibc rwlocks, lock starvation happened for the writers with reader-preferring rwlocks. For writer preferring rwlocks, the WW futex one fared better. The Glibc one, however, was close to starving the readers.

Lock starvation should not happen on the TP futexes as long as the underlying kernel mutex is lock starvation free which is the case for 4.10 and later kernels.

On a 2-socket 10-core and 80-thread Power8 system, the benchmark results were:

                          WW futex       TP futex         Glibc
                          --------       --------         -----
Total locking ops         8,713,156     18,897,676      4,707,780
Per-thread avg/sec           10,888         23,598          5,884
Per-thread min/sec            9,097         22,028          5,763
Per-thread max/sec           13,106         24,208          6,007
Write lock slowpaths      2,100,149      4,078,698          -
Write unlock slowpaths    2,324,093              6          -
Read lock slowpaths         500,372      2,160,265          -
Read unlock slowpaths     1,210,698              1          -

In this case, the TP futex is more than 2X the performance of the WW futex.

---
 Documentation/tp-futex.txt | 170 +++++++++++++++++++++++++++++++++++++++------
 1 file changed, 150 insertions(+), 20 deletions(-)
Documentation/tp-futex.txt +150 -20
index d040c18..5c781a5 100644
@@ -80,22 +80,62 @@ Userspace locking can improve throughput by reducing lock hold time,
 but it also has the risk of lock starvation. So kernel locking should
 be used after a number of userspace locking failures.
 
-Inside the kernel, a kernel mutex is used for serialization among
-the futex waiters. Only the top lock waiter which is the owner of
-the serialization mutex is allowed to continuously spin and attempt
-to acquire the lock.  Other lock waiters will have one attempt to
-steal the lock before entering the mutex queues.
+A shared lock acquirer, on the other hand, has to do either one of
+the following 2 actions depends on the current value of the futex:
+
+ 1) If current futex value is 0, atomically put (FUTEX_SHARED + 1)
+    into the lower 30 bits of the futex.
+
+ 2) If the FUTEX_SHARED bit is set and the FUTEX_SHARED_UNLOCK bit
+    isn't, atomically increment the reader count in the lower 24 bits.
+    The other unused bits (24-27) can be used for other management purpose
+    and will be ignored by the kernel.
+
+Any failure to both of the above actions will lead to the following
+new FUTEX_LOCK_SHARED futex(2) syscall.
+
+  futex(uaddr, FUTEX_LOCK_SHARED, prefer_reader, timeout, NULL, 0);
+
+The special FUTEX_SHARED bit (bit 30) is used to distinguish between a
+shared lock and an exclusive lock. If the FUTEX_SHARED bit isn't set,
+the lower 29 bits contain the TID of the exclusive lock owner. If
+the FUTEX_SHARED bit is set, the lower 29 bits contain the number of
+tasks owning the shared lock.  Therefore, only 29 bits are allowed
+to be used by the TID.
+
+The FUTEX_SHARED_UNLOCK bit can be used by userspace to prevent racing
+and to indicate that a shared unlock is in progress as it will disable
+any shared trylock attempt in the kernel.
+
+The prefer_reader flag is used to control if the lock acquisition
+should be done in a prefer reader mode (if set) or a neutral mode
+where there is no preference to either reader or writer.
+
+Inside the kernel, a kernel mutex is used for serialization among the
+futex waiters. Only the top lock waiter which is the owner of the
+serialization mutex is allowed to continuously spin and attempt to
+acquire the lock.  Other lock waiters will have one or two attempts
+to steal the lock before entering the mutex queues.
 
 When the exclusive futex lock owner is no longer running, the top
 waiter will set the FUTEX_WAITERS bit before going to sleep. This is
 to make sure the futex owner will go into the kernel at unlock time
 to wake up the top waiter.
 
+When the futex is shared by multiple owners, there is no way to
+determine if all the shared owners are running or not. In this case,
+the top waiter will spin for a fixed amount of time if no reader
+activity is observed before giving up and going to sleep. Any change
+in the reader count will be considered reader activities and so will
+reset the timer. However, when the hand-off threshold has been reached,
+reader optimistic spinning timer reset will be disabled automatically.
+
 The return values of the above futex locking syscall, if non-negative,
-are status code that consists of 2 fields - the lock acquisition code
-(bits 0-7) and the number of sleeps (bits 8-30) in the optimistic
-spinning loop before acquiring the futex. A negative returned value
-means an error has happened.
+are status code that consists of 3 fields - the lock acquisition code
+(bits 0-7) and the number of sleeps (bits 16-30) in the optimistic
+spinning loop before acquiring the futex. Bits 8-15 are reserved
+for future extension. A negative returned value means an error has
+happened.
 
 The lock acquisition code can have the following values:
  a) 0 - lock stolen as non-top waiter
@@ -108,14 +148,21 @@ issue a FUTEX_UNLOCK futex(2) syscall to wake up the top waiter.
 
   futex(uaddr, FUTEX_UNLOCK, 0, NULL, NULL, 0);
 
-A return value of 1 from the FUTEX_UNLOCK futex(2) syscall indicates
-a task has been woken up. The syscall returns 0 if no sleeping task
-is woken. A negative value will be returned if an error happens.
+For a shared lock owner, unlock is done by atomically decrementing
+the reader count by one. If the resultant futex value has no reader
+remaining, the process has to atomically change the futex value from
+FUTEX_SHARED to 0. If that fails, it has to issue a FUTEX_UNLOCK_SHARED
+futex(2) syscall to wake up the top waiter.
+
+A return value of 1 from the FUTEX_UNLOCK or FUTEX_UNLOCK_SHARED
+futex(2) syscall indicates a task has been woken up. The syscall
+returns 0 if no sleeping task is woken. A negative value will be
+returned if an error happens.
 
-The error number returned by a FUTEX_UNLOCK syscall on an empty futex
-can be used to decide if the TP futex functionality is implemented
-in the kernel. If it is present, an EPERFM error will be returned.
-Otherwise it will return ENOSYS.
+The error number returned by a FUTEX_UNLOCK or FUTEX_UNLOCK_SHARED
+syscall on an empty futex can be used to decide if the TP futex
+functionality is implemented in the kernel. If it is present, an
+EPERFM error will be returned.  Otherwise it will return ENOSYS.
 
 TP futexes require the kernel to have SMP support as well as support
 for the cmpxchg functionality. For architectures that don't support
@@ -137,6 +184,13 @@ situation. It is possible for the kernel to handle this internally,
 but it will probably reduce performance. Userspace code can handle
 this more efficiently.
 
+Shared locking TP futexes, on the other hand, cannot be used with
+robust futexes. The unexpected death of a shared lock owner may
+leave the futex permanently in the shared state leading to deadlock
+of exclusive lock waiters. If necessary, some kind of timeout and
+userspace lock management system have to be used to resolve this
+deadlock problem.
+
 Usage Scenario
 --------------
 
@@ -148,6 +202,24 @@ used when the critical section is long and prone to sleeping. However,
 it may not have the performance gain when compared with a wait-wake
 futex in this case.
 
+A TP futex can also be used to implement a userspace reader-writer
+lock (rwlock) where the writers use the FUTEX_LOCK and FUTEX_UNLOCK
+futex(2) syscalls and the readers used the FUTEX_LOCK_SHARED and
+FUTEX_UNLOCK_SHARED futex(2) syscalls. Beside providing a better
+performance compared with wait-wake futexes, other advantages of
+using the the TP futexes for rwlocks are:
+
+ 1) The simplicity and ease of maintenance of the userspace locking
+    codes. There is only one version of rwlock using TP futex versus
+    a reader-preferring variant and/or a writer-preferring variant
+    when using the wait-wake futexes.
+
+ 2) TP futex is lock-starvation free. That may not be the case
+    with rwlocks implemented with wait-wake futexes especially the
+    reader-preferring variants where the writers can be starved of
+    the lock under certain circumstances. Some writer-preferring
+    rwlock variants may also starve readers of getting the lock.
+
 The wait-wake futexes are more versatile as they can also be used to
 implement other locking primitives like semaphores or conditional
 variables.  So the TP futex is not a direct replacement of the
@@ -157,12 +229,12 @@ futex is likely a better option than the wait-wake futex.
 Sample Code
 -----------
 
-The following are sample code to implement simple mutex lock and
-unlock functions.
+The following are sample code to implement simple mutex or writer
+lock and unlock functions.
 
 __thread int thread_id;
 
-void mutex_lock(int *faddr)
+void write_lock(int *faddr)
 {
 	if (cmpxchg(faddr, 0, thread_id) == 0)
 		return;
@@ -170,7 +242,7 @@ void mutex_lock(int *faddr)
 		;
 }
 
-void mutex_unlock(int *faddr)
+void write_unlock(int *faddr)
 {
 	int old, fval;
 
@@ -178,3 +250,61 @@ void mutex_unlock(int *faddr)
 		return;
 	futex(faddr, FUTEX_UNLOCK, 0, NULL, NULL, 0);
 }
+
+The following sample code can be used to implement shared or reader
+lock and unlock functions.
+
+void read_lock(int *faddr)
+{
+	int val = cmpxchg(faddr, 0, FUTEX_SHARED + 1);
+
+	if (!val)
+		return;
+	for (;;) {
+		int old = val, new;
+
+		if (!old)
+			new = FUTEX_SHARED + 1;
+		else if (!(old & FUTEX_SHARED) ||
+			 (old & ((~FUTEX_SHARED_TID_MASK)|
+				   FUTEX_SHARED_UNLOCK)))
+			break;
+		else
+			new = old + 1;
+		val = cmpxchg(faddr, old, new);
+		if (val == old)
+			return;
+	}
+	while (futex(faddr, FUTEX_LOCK_SHARED, ...) < 0)
+		;
+}
+
+void read_unlock(int *faddr)
+{
+	int val = xadd(faddr, -1) - 1;
+	int old;
+
+	for (;;) {
+		/*
+		 * Return if not the last reader, not in shared locking
+		 * mode or the unlock bit has been set.
+		 */
+		if (!(val & FUTEX_SHARED) ||
+		     (val & (FUTEX_SHARED_UNLOCK|FUTEX_SCNT_MASK)))
+			return;
+		if (val & ~FUTEX_SHARED_TID_MASK) {
+			old = cmpxchg(faddr, val, val|FUTEX_SHARED_UNLOCK);
+			if (old == val)
+				break;	/* Do FUTEX_UNLOCK_SHARED */
+		} else {
+			old = cmpxchg(faddr, val, 0);
+			if (old == val)
+				return;
+		}
+		val = old;
+	}
+	futex(faddr, FUTEX_UNLOCK_SHARED, ...);
+}
+
+More elaborate sample locking codes for the TP futexes are also
+available at the tools/perf/bench/futex-locks.c file.
Signature · 1 line
1.8.3.1
#22 lore raw

PATCH v6 20/22 perf bench: Extend mutex/rwlock futex suite to test TP futexes

In reply to Waiman Long

This patch extends the futex-mutex and futex-rwlock microbenchmarks to test userspace mutexes and rwlocks built on top of the TP futexes. We can then compare the relative performance of those userspace locks based on different type of futexes.

---
 tools/perf/bench/futex-locks.c | 251 +++++++++++++++++++++++++++++++++++++++--
 tools/perf/bench/futex.h       |  45 ++++++++
 tools/perf/check-headers.sh    |   4 +
 3 files changed, 292 insertions(+), 8 deletions(-)
tools/perf/bench/futex-locks.c +243 -8
index 88f54c1..bc99be5 100644
@@ -8,12 +8,14 @@
  * indication of actual throughput of the mutex code as it may not really
  * need to call into the kernel. Therefore, 3 sets of simple mutex lock and
  * unlock functions are written to implenment a mutex lock using the
- * wait-wake (2 versions) and PI futexes respectively. These functions serve
- * as the basis for measuring the locking throughput.
+ * wait-wake, PI, GC and TP futexes respectively. These functions serve as
+ * the basis for measuring the locking throughput.
  *
- * Two sets of simple reader/writer lock and unlock functions are also
- * implemented using the wait-wake futexes as well as the Glibc rwlock
- * respectively for performance measurement purpose.
+ * Three sets of simple reader/writer lock and unlock functions are also
+ * implemented using the wait-wake and TP futexes as well as the Glibc
+ * rwlock respectively for performance measurement purpose. The TP futex
+ * writer lock/unlock functions are the same as that of the mutex functions.
+ * That is not the case for the wait-wake futexes.
  */
 
 #include <pthread.h>
@@ -50,6 +52,7 @@ enum {
 	STAT_OPS,	/* # of exclusive locking operations	*/
 	STAT_SOPS,	/* # of shared locking operations	*/
 	STAT_LOCKS,	/* # of exclusive lock slowpath count	*/
+	STAT_KLOCKS,	/* # of exclusive kernel locks		*/
 	STAT_UNLOCKS,	/* # of exclusive unlock slowpath count	*/
 	STAT_SLEEPS,	/* # of exclusive lock sleeps		*/
 	STAT_SLOCKS,	/* # of shared lock slowpath count	*/
@@ -57,6 +60,12 @@ enum {
 	STAT_SSLEEPS,	/* # of shared lock sleeps		*/
 	STAT_EAGAINS,	/* # of EAGAIN errors			*/
 	STAT_WAKEUPS,	/* # of wakeups (unlock return)		*/
+	STAT_HANDOFFS,	/* # of lock handoff (TP only)		*/
+	STAT_STEALS,	/* # of lock steals (TP only)		*/
+	STAT_SLRETRIES,	/* # of shared lock retries		*/
+	STAT_SURETRIES,	/* # of shared unlock retries		*/
+	STAT_SALONES,	/* # of shared locking alone ops	*/
+	STAT_SGROUPS,	/* # of shared locking group ops	*/
 	STAT_TIMEOUTS,	/* # of exclusive lock timeouts		*/
 	STAT_LOCKERRS,	/* # of exclusive lock errors		*/
 	STAT_UNLKERRS,	/* # of exclusive unlock errors		*/
@@ -225,7 +234,7 @@ static inline void stat_inc(int tid __maybe_unused, int item __maybe_unused)
  */
 static const struct option mutex_options[] = {
 	OPT_INTEGER ('d', "locklat",	&locklat,  "Specify inter-locking latency (default = 1)"),
-	OPT_STRING  ('f', "ftype",	&ftype,    "type", "Specify futex type: WW, PI, GC, all (default)"),
+	OPT_STRING  ('f', "ftype",	&ftype,    "type", "Specify futex type: WW, PI, GC, TP, all (default)"),
 	OPT_INTEGER ('l', "loadlat",	&loadlat,  "Specify load latency (default = 1)"),
 	OPT_UINTEGER('r', "runtime",	&nsecs,    "Specify runtime (in seconds, default = 10s)"),
 	OPT_BOOLEAN ('S', "shared",	&fshared,  "Use shared futexes instead of private ones"),
@@ -244,7 +253,7 @@ static inline void stat_inc(int tid __maybe_unused, int item __maybe_unused)
 
 static const struct option rwlock_options[] = {
 	OPT_INTEGER ('d', "locklat",	&locklat,  "Specify inter-locking latency (default = 1)"),
-	OPT_STRING  ('f', "ftype",	&ftype,    "type", "Specify futex type: WW, GC, all (default)"),
+	OPT_STRING  ('f', "ftype",	&ftype,    "type", "Specify futex type: WW, TP, GC, all (default)"),
 	OPT_INTEGER ('l', "loadlat",	&loadlat,  "Specify load latency (default = 1)"),
 	OPT_UINTEGER('R', "read-%",	&rpercent, "Specify reader percentage (default 50%)"),
 	OPT_UINTEGER('r', "runtime",	&nsecs,    "Specify runtime (in seconds, default = 10s)"),
@@ -522,6 +531,78 @@ static void gc_mutex_unlock(futex_t *futex __maybe_unused,
 	pthread_mutex_unlock(&mutex);
 }
 
+/*
+ * TP futex lock/unlock functions
+ */
+static void tp_mutex_lock(futex_t *futex, int tid)
+{
+	futex_t val = 0;
+	int ret;
+	int uscnt = 4;	/* Do 4 userspace locks before doing kernel lock */
+
+	if (atomic_cmpxchg_acquire(futex, &val, thread_id))
+		return;
+
+	/*
+	 * Retry if an error happens
+	 */
+	stat_inc(tid, STAT_LOCKS);
+	for (;;) {
+		FUTEX_CALL(futex_lock, TIME_LOCK, futex, uscnt, ptospec, flags);
+		if (uscnt)
+			uscnt--;
+		else
+			stat_inc(tid, STAT_KLOCKS);
+
+		if (likely(ret >= 0))
+			break;
+		if (errno == EAGAIN) {
+			stat_inc(tid, STAT_EAGAINS);
+			val = 0;
+			if (atomic_cmpxchg_acquire(futex, &val, thread_id))
+				return;
+			continue;
+		}
+		if (errno == ETIMEDOUT) {
+			stat_inc(tid, STAT_TIMEOUTS);
+		} else {
+			stat_inc(tid, STAT_LOCKERRS);
+		}
+		/*
+		 * It is possible, though extremely unlikely, that a lock
+		 * handoff has happened and an error is returned. So we
+		 * need to check for that here.
+		 */
+		if ((*futex & FUTEX_SHARED_TID_MASK) == thread_id)
+			return;
+	}
+	/*
+	 * Get # of sleeps & locking method
+	 */
+	stat_add(tid, STAT_SLEEPS, ret >> 16);
+	ret &= 0xff;
+	if (!ret)
+		stat_inc(tid, STAT_STEALS);
+	else if (ret == 2)
+		stat_inc(tid, STAT_HANDOFFS);
+}
+
+static void tp_mutex_unlock(futex_t *futex, int tid)
+{
+	futex_t val = thread_id;
+	int ret;
+
+	if (atomic_cmpxchg_release(futex, &val, 0))
+		return;
+
+	stat_inc(tid, STAT_UNLOCKS);
+	FUTEX_CALL(futex_unlock, TIME_UNLK, futex, flags);
+	if (ret < 0)
+		stat_inc(tid, STAT_UNLKERRS);
+	else
+		stat_add(tid, STAT_WAKEUPS, ret);
+}
+
 /**********************[ RWLOCK lock/unlock functions ]********************/
 
 /*
@@ -814,6 +895,121 @@ static void ww2_read_lock(futex_t *futex __maybe_unused, int tid)
 }
 
 /*
+ * TP futex reader/writer lock/unlock functions
+ */
+#define tp_write_lock		tp_mutex_lock
+#define tp_write_unlock		tp_mutex_unlock
+#define FUTEX_FLAGS_MASK	(3UL << 30)
+
+static void tp_read_lock(futex_t *futex, int tid)
+{
+	futex_t val = 0;
+	int ret, retry = 0;
+
+	if (atomic_cmpxchg_acquire(futex, &val, FUTEX_SHARED + 1))
+		return;
+
+	for (;;) {
+		futex_t new;
+
+		/*
+		 * Try to increment the reader count only if
+		 * 1) the FUTEX_SHARED bit is set; and
+		 * 2) none of the flags bits or FUTEX_SHARED_UNLOCK is set.
+		 */
+		if (!val)
+			new = FUTEX_SHARED + 1;
+		else if ((val & FUTEX_SHARED) &&
+			!(val & (FUTEX_FLAGS_MASK|FUTEX_SHARED_UNLOCK)))
+			new = val + 1;
+		else
+			break;
+		if (atomic_cmpxchg_acquire(futex, &val, new))
+			goto out;
+		retry++;
+	}
+
+	stat_inc(tid, STAT_SLOCKS);
+	for (;;) {
+		FUTEX_CALL(futex_lock_shared, TIME_SLOCK,
+			   futex, !pwriter, ptospec, flags);
+		if (likely(ret >= 0))
+			break;
+		if (errno == ETIMEDOUT)
+			stat_inc(tid, STAT_STIMEOUTS);
+		else
+			stat_inc(tid, STAT_SLOCKERRS);
+	}
+	/*
+	 * Get # of sleeps & locking method
+	 */
+	stat_add(tid, STAT_SSLEEPS, ret >> 16);
+	if (ret & 0x100)
+		 stat_inc(tid, STAT_SALONES);	/* In alone mode */
+	if (ret & 0x200)
+		 stat_inc(tid, STAT_SGROUPS);	/* In group mode */
+
+	ret &= 0xff;
+	if (!ret)
+		stat_inc(tid, STAT_STEALS);
+	else if (ret == 2)
+		stat_inc(tid, STAT_HANDOFFS);
+out:
+	if (unlikely(retry))
+		stat_add(tid, STAT_SLRETRIES, retry);
+}
+
+static void tp_read_unlock(futex_t *futex, int tid)
+{
+	futex_t val;
+	int ret, retry = 0;
+
+	val = atomic_dec_release(futex);
+	if (!(val & FUTEX_SHARED)) {
+		fprintf(stderr,
+			"tp_read_unlock: Incorrect futex value = 0x%x\n", val);
+		exit(1);
+	}
+
+	for (;;) {
+		/*
+		 * Return if not the last reader, not in shared locking
+		 * mode or the unlock bit has been set.
+		 */
+		if ((val & (FUTEX_SCNT_MASK|FUTEX_SHARED_UNLOCK)) ||
+		   !(val & FUTEX_SHARED))
+			return;
+
+		if (val & ~FUTEX_SHARED_TID_MASK) {
+			/*
+			 * Only one task that can set the FUTEX_SHARED_UNLOCK
+			 * bit will do the unlock.
+			 */
+			if (atomic_cmpxchg_relaxed(futex, &val,
+					val|FUTEX_SHARED_UNLOCK))
+				break;
+		} else {
+			/*
+			 * Try to clear the futex.
+			 */
+			if (atomic_cmpxchg_release(futex, &val, 0))
+				goto out;
+		}
+		retry++;
+	}
+
+	stat_inc(tid, STAT_SUNLOCKS);
+	FUTEX_CALL(futex_unlock_shared, TIME_SUNLK, futex, flags);
+	if (ret < 0)
+		stat_inc(tid, STAT_SUNLKERRS);
+	else
+		stat_add(tid, STAT_WAKEUPS, ret);
+out:
+	if (unlikely(retry))
+		stat_add(tid, STAT_SURETRIES, retry);
+}
+
+/*
  * Glibc read/write lock
  */
 static void gc_write_lock(futex_t *futex __maybe_unused,
@@ -1050,6 +1246,20 @@ static int futex_mutex_type(const char **ptype)
 		}
 		pthread_mutex_init(&mutex, attr);
 		mutex_inited = true;
+	} else if (!strcasecmp(type, "TP")) {
+		*ptype = "TP";
+		mutex_lock_fn = tp_mutex_lock;
+		mutex_unlock_fn = tp_mutex_unlock;
+
+		/*
+		 * Check if TP futex is supported.
+		 */
+		futex_unlock(&global_futex, 0);
+		if (errno == ENOSYS) {
+			fprintf(stderr,
+			    "\nTP futexes are not supported by the kernel!\n");
+			return -1;
+		}
 	} else {
 		return -1;
 	}
@@ -1074,6 +1284,22 @@ static int futex_rwlock_type(const char **ptype)
 			write_lock_fn = ww_write_lock;
 			write_unlock_fn = ww_write_unlock;
 		}
+	} else if (!strcasecmp(type, "TP")) {
+		*ptype = "TP";
+		read_lock_fn = tp_read_lock;
+		read_unlock_fn = tp_read_unlock;
+		write_lock_fn = tp_write_lock;
+		write_unlock_fn = tp_write_unlock;
+
+		/*
+		 * Check if TP futex is supported.
+		 */
+		futex_unlock(&global_futex, 0);
+		if (errno == ENOSYS) {
+			fprintf(stderr,
+			    "\nTP futexes are not supported by the kernel!\n");
+			return -1;
+		}
 	} else if (!strcasecmp(type, "GC")) {
 		pthread_rwlockattr_t *attr = NULL;
 
@@ -1118,6 +1344,7 @@ static void futex_test_driver(const char *futex_type,
 		[STAT_OPS]	 = "Total exclusive locking ops",
 		[STAT_SOPS]	 = "Total shared locking ops",
 		[STAT_LOCKS]	 = "Exclusive lock slowpaths",
+		[STAT_KLOCKS]	 = "Exclusive kernel locks",
 		[STAT_UNLOCKS]	 = "Exclusive unlock slowpaths",
 		[STAT_SLEEPS]	 = "Exclusive lock sleeps",
 		[STAT_SLOCKS]	 = "Shared lock slowpaths",
@@ -1125,7 +1352,13 @@ static void futex_test_driver(const char *futex_type,
 		[STAT_SSLEEPS]	 = "Shared lock sleeps",
 		[STAT_WAKEUPS]	 = "Process wakeups",
 		[STAT_EAGAINS]	 = "EAGAIN lock errors",
-		[STAT_TIMEOUTS]	 = "Exclusive lock timeouts",
+		[STAT_HANDOFFS]  = "Lock handoffs",
+		[STAT_STEALS]	 = "Lock stealings",
+		[STAT_SLRETRIES] = "Shared lock retries",
+		[STAT_SURETRIES] = "Shared unlock retries",
+		[STAT_SALONES]	 = "Shared lock ops (alone mode)",
+		[STAT_SGROUPS]	 = "Shared lock ops (group mode)",
+		[STAT_TIMEOUTS]  = "Exclusive lock timeouts",
 		[STAT_STIMEOUTS] = "Shared lock timeouts",
 		[STAT_LOCKERRS]  = "\nExclusive lock errors",
 		[STAT_UNLKERRS]  = "\nExclusive unlock errors",
@@ -1407,6 +1640,7 @@ int bench_futex_mutex(int argc, const char **argv,
 		futex_test_driver("WW", futex_mutex_type, mutex_workerfn);
 		futex_test_driver("PI", futex_mutex_type, mutex_workerfn);
 		futex_test_driver("GC", futex_mutex_type, mutex_workerfn);
+		futex_test_driver("TP", futex_mutex_type, mutex_workerfn);
 	} else {
 		futex_test_driver(ftype, futex_mutex_type, mutex_workerfn);
 	}
@@ -1433,6 +1667,7 @@ int bench_futex_rwlock(int argc, const char **argv,
 
 	if (!ftype || !strcmp(ftype, "all")) {
 		futex_test_driver("WW", futex_rwlock_type, rwlock_workerfn);
+		futex_test_driver("TP", futex_rwlock_type, rwlock_workerfn);
 		futex_test_driver("GC", futex_rwlock_type, rwlock_workerfn);
 	} else {
 		futex_test_driver(ftype, futex_rwlock_type, rwlock_workerfn);
tools/perf/bench/futex.h +45 -0

51 lines not on this page · raw message

tools/perf/check-headers.sh +4 -0
index c747bfd..6ec5f2d 100755
@@ -57,3 +57,7 @@ check arch/x86/lib/memcpy_64.S        -B -I "^EXPORT_SYMBOL" -I "^#include <asm/
 check arch/x86/lib/memset_64.S        -B -I "^EXPORT_SYMBOL" -I "^#include <asm/export.h>"
 check include/uapi/asm-generic/mman.h -B -I "^#include <\(uapi/\)*asm-generic/mman-common.h>"
 check include/uapi/linux/mman.h       -B -I "^#include <\(uapi/\)*asm/mman.h>"
+
+# need to link to the latest futex.h header
+[[ -f ../../include/uapi/linux/futex.h ]] &&
+	ln -sf  ../../../../../include/uapi/linux/futex.h util/include/linux
Signature · 1 line
1.8.3.1
#23 lore raw

PATCH v6 21/22 sched, TP-futex: Make wake_up_q() return wakeup count

In reply to Waiman Long

Unlike wake_up_process(), wake_up_q() doesn't tell us how many tasks have been woken up. This information can sometimes be useful for tracking purpose. So wake_up_q() is now modified to return that information.

---
 include/linux/sched/wake_q.h | 2 +-
 kernel/futex.c               | 8 +++-----
 kernel/sched/core.c          | 6 ++++--
 3 files changed, 8 insertions(+), 8 deletions(-)
include/linux/sched/wake_q.h +1 -1
index d03d8a9..8b88fc0 100644
@@ -48,6 +48,6 @@ static inline void wake_q_init(struct wake_q_head *head)
 
 extern void wake_q_add(struct wake_q_head *head,
 		       struct task_struct *task);
-extern void wake_up_q(struct wake_q_head *head);
+extern int wake_up_q(struct wake_q_head *head);
 
 #endif /* _LINUX_SCHED_WAKE_Q_H */
kernel/futex.c +3 -5
index 984b836..a199574 100644
@@ -4298,11 +4298,9 @@ static int futex_unlock(u32 __user *uaddr, unsigned int flags,
 out_put_key:
 	put_futex_key(&key);
 	if (owner) {
-		/*
-		 * No error would have happened if owner defined.
-		 */
-		wake_up_q(&wake_q);
-		return ret ? ret : 1;
+		int cnt = wake_up_q(&wake_q);
+
+		return ret ? ret : cnt;
 	}
 
 	return ret;
kernel/sched/core.c +4 -2
index 6d6cad9..2193d59 100644
@@ -447,9 +447,10 @@ void wake_q_add(struct wake_q_head *head, struct task_struct *task)
 	head->lastp = &node->next;
 }
 
-void wake_up_q(struct wake_q_head *head)
+int wake_up_q(struct wake_q_head *head)
 {
 	struct wake_q_node *node = head->first;
+	int wakecnt = 0;
 
 	while (node != WAKE_Q_TAIL) {
 		struct task_struct *task;
@@ -464,9 +465,10 @@ void wake_up_q(struct wake_q_head *head)
 		 * wake_up_process() implies a wmb() to pair with the queueing
 		 * in wake_q_add() so as not to miss wakeups.
 		 */
-		wake_up_process(task);
+		wakecnt += wake_up_process(task);
 		put_task_struct(task);
 	}
+	return wakecnt;
 }
 
 /*
Signature · 1 line
1.8.3.1
#24 lore raw

PATCH v6 22/22 futex: Dump internal futex state via debugfs

In reply to Waiman Long

For debugging purpose, it is sometimes useful to dump the internal states in the futex hash bucket table. This patch adds a file "futex_hash_table" in debugfs root filesystem to dump the internal futex states.

---
 kernel/futex.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)
kernel/futex.c +82 -0
index a199574..7691a70 100644
@@ -4474,3 +4474,85 @@ static int __init futex_init(void)
 	return 0;
 }
 core_initcall(futex_init);
+
+#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_SMP)
+/*
+ * Debug code to dump selected content of in-kernel futex hash bucket table.
+ */
+#include <linux/debugfs.h>
+
+static int futex_dump_show(struct seq_file *m, void *arg)
+{
+	struct futex_hash_bucket *hb = arg;
+	struct futex_state *state;
+	int i;
+
+	if (list_empty(&hb->fs_head))
+		return 0;
+
+	seq_printf(m, "\nHash bucket %d:\n", (int)(hb - futex_queues));
+	spin_lock(&hb->fs_lock);
+	i = 0;
+	list_for_each_entry(state, &hb->fs_head, fs_list) {
+		seq_printf(m, "  Futex state %d\n", i++);
+		if (state->owner)
+			seq_printf(m, "    owner PID = %d\n",
+				   task_pid_vnr(state->owner));
+		if (state->mutex_owner)
+			seq_printf(m, "    mutex owner PID = %d\n",
+				   task_pid_vnr(state->mutex_owner));
+		seq_printf(m, "    reference count = %d\n",
+			   atomic_read(&state->refcount));
+		seq_printf(m, "    handoff PID = %d\n", state->handoff_pid);
+	}
+	spin_unlock(&hb->fs_lock);
+	return 0;
+}
+
+static void *futex_dump_start(struct seq_file *m, loff_t *pos)
+{
+	return (*pos < futex_hashsize) ? &futex_queues[*pos] : NULL;
+}
+
+static void *futex_dump_next(struct seq_file *m, void *arg, loff_t *pos)
+{
+	(*pos)++;
+	return (*pos < futex_hashsize) ? &futex_queues[*pos] : NULL;
+}
+
+static void futex_dump_stop(struct seq_file *m, void *arg)
+{
+}
+
+static const struct seq_operations futex_dump_op = {
+	.start = futex_dump_start,
+	.next  = futex_dump_next,
+	.stop  = futex_dump_stop,
+	.show  = futex_dump_show,
+};
+
+static int futex_dump_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &futex_dump_op);
+}
+
+static const struct file_operations fops_futex_dump = {
+	.open	 = futex_dump_open,
+	.read	 = seq_read,
+	.llseek	 = seq_lseek,
+	.release = seq_release,
+};
+
+/*
+ * Initialize debugfs for the futex hash bucket table dump.
+ */
+static int __init init_futex_dump(void)
+{
+	if (!debugfs_create_file("futex_hash_table", 0400, NULL, NULL,
+				 &fops_futex_dump))
+		return -ENOMEM;
+	return 0;
+}
+fs_initcall(init_futex_dump);
+
+#endif /* CONFIG_DEBUG_FS && CONFIG_SMP */
Signature · 1 line
1.8.3.1