Discussion:
[PATCH] nptl: New test nptl/tst-stack-usage
Florian Weimer
2018-12-08 16:14:02 UTC
Permalink
Results for x86-64 with AVX2 (kernel-4.19.5-300.fc29):

info: stack size: 524288
info: PTHREAD_STACK_MIN: 16384
info: MINSIGSTKSZ: 2048
info: SIGSTKSZ: 8192
info: measuring stack usage, no signal delivery
lowest modification offset ranges from 519904 to 519920
highest modification offset ranges from 523675 to 524288
minimum modified range: 3755
maximum modified range: 4384
info: measuring stack usage, with signal delivery
lowest modification offset ranges from 517944 to 517945
highest modification offset ranges from 523675 to 524288
minimum modified range: 5731
maximum modified range: 6344
info: estimated signal delivery stack overhead: 1960 bytes
info: measuring stack usage, no-cancel test
lowest modification offset ranges from 519720 to 519736
highest modification offset ranges from 523675 to 524288
minimum modified range: 3939
maximum modified range: 4568
info: measuring stack usage, cancellation test
lowest modification offset ranges from 516048 to 516049
highest modification offset ranges from 523675 to 524288
minimum modified range: 7627
maximum modified range: 8240
info: estimated cancellation stack overhead: 3672 bytes

Results for x86-64 with AVX-512F (kernel-4.18.0-48.el8):

info: stack size: 524288
info: PTHREAD_STACK_MIN: 16384
info: MINSIGSTKSZ: 2048
info: SIGSTKSZ: 8192
info: measuring stack usage, no signal delivery
lowest modification offset ranges from 519920 to 519936
highest modification offset ranges from 523675 to 524288
minimum modified range: 3739
maximum modified range: 4368
info: measuring stack usage, with signal delivery
lowest modification offset ranges from 516472 to 516473
highest modification offset ranges from 523675 to 524288
minimum modified range: 7202
maximum modified range: 7816
info: estimated signal delivery stack overhead: 3448 bytes
info: measuring stack usage, no-cancel test
lowest modification offset ranges from 519736 to 519752
highest modification offset ranges from 523675 to 524288
minimum modified range: 3923
maximum modified range: 4552
info: measuring stack usage, cancellation test
lowest modification offset ranges from 514448 to 518120
highest modification offset ranges from 523675 to 524288
minimum modified range: 6168
maximum modified range: 9840
info: estimated cancellation stack overhead: 5288 bytes

Results for POWER8 BE (kernel-3.10.0-957.1.3.el7):

info: stack size: 524288
info: PTHREAD_STACK_MIN: 131072
info: MINSIGSTKSZ: 4096
info: SIGSTKSZ: 16384
info: measuring stack usage, no signal delivery
lowest modification offset ranges from 517824 to 517826
highest modification offset ranges from 522528 to 522624
minimum modified range: 4702
maximum modified range: 4800
info: measuring stack usage, with signal delivery
lowest modification offset ranges from 513232 to 513234
highest modification offset ranges from 522528 to 522624
minimum modified range: 9294
maximum modified range: 9392
info: estimated signal delivery stack overhead: 4592 bytes
info: measuring stack usage, no-cancel test
lowest modification offset ranges from 516976 to 516978
highest modification offset ranges from 522528 to 522624
minimum modified range: 5550
maximum modified range: 5648
info: measuring stack usage, cancellation test
lowest modification offset ranges from 503600 to 503602
highest modification offset ranges from 522528 to 522624
minimum modified range: 18926
maximum modified range: 19024
info: estimated cancellation stack overhead: 13376 bytes

Results for POWER9 LE (kernel-4.18.0-48.el8):

info: stack size: 524288
info: PTHREAD_STACK_MIN: 131072
info: MINSIGSTKSZ: 4096
info: SIGSTKSZ: 16384
info: measuring stack usage, no signal delivery
lowest modification offset ranges from 517984 to 517985
highest modification offset ranges from 522526 to 522624
minimum modified range: 4542
maximum modified range: 4640
info: measuring stack usage, with signal delivery
lowest modification offset ranges from 513472 to 513473
highest modification offset ranges from 522526 to 522624
minimum modified range: 9054
maximum modified range: 9152
info: estimated signal delivery stack overhead: 4512 bytes
info: measuring stack usage, no-cancel test
lowest modification offset ranges from 517296 to 517297
highest modification offset ranges from 522526 to 522624
minimum modified range: 5230
maximum modified range: 5328
info: measuring stack usage, cancellation test
lowest modification offset ranges from 506144 to 506145
highest modification offset ranges from 522526 to 522624
minimum modified range: 16381
maximum modified range: 16480
info: estimated cancellation stack overhead: 11152 bytes

2018-12-08 Florian Weimer <***@redhat.com>

* nptl/tst-stack-usage.c: New file.
* nptl/Makefile (tests): Add tst-stack-usage.
(tst-stack-usage): Link with -z now.
* support/Makefile (libsupport-routines): Add
xpthread_attr_setstack.
* support/xthread.h (xpthread_attr_setstack): Declare.
* support/xpthread_attr_setstack.c: New file.

diff --git a/nptl/Makefile b/nptl/Makefile
index 34ae830276..f994db59cc 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -318,7 +318,8 @@ tests = tst-attr1 tst-attr2 tst-attr3 tst-default-attr \
tst-minstack-throw \
tst-cnd-basic tst-mtx-trylock tst-cnd-broadcast \
tst-cnd-timedwait tst-thrd-detach tst-mtx-basic tst-thrd-sleep \
- tst-mtx-recursive tst-tss-basic tst-call-once tst-mtx-timedlock
+ tst-mtx-recursive tst-tss-basic tst-call-once tst-mtx-timedlock \
+ tst-stack-usage

tests-internal := tst-rwlock19 tst-rwlock20 \
tst-sem11 tst-sem12 tst-sem13 \
@@ -722,6 +723,9 @@ $(objpfx)tst-audit-threads: $(objpfx)tst-audit-threads-mod2.so
$(objpfx)tst-audit-threads.out: $(objpfx)tst-audit-threads-mod1.so
tst-audit-threads-ENV = LD_AUDIT=$(objpfx)tst-audit-threads-mod1.so

+# Disable lazy binding to avoid measuring ld.so stack overhead.
+LDFLAGS-tst-stack-usage = -Wl,-z,now
+
# The tests here better do not run in parallel
ifneq ($(filter %tests,$(MAKECMDGOALS)),)
.NOTPARALLEL:
diff --git a/nptl/tst-stack-usage.c b/nptl/tst-stack-usage.c
new file mode 100644
index 0000000000..80f853f595
--- /dev/null
+++ b/nptl/tst-stack-usage.c
@@ -0,0 +1,238 @@
+/* Measure the stack size used by signal delivery and thread cancellation.
+ Copyright (C) 2018 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/xsignal.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <sys/mman.h>
+
+/* Initialized by do_test below. */
+static unsigned char *stack_base;
+static size_t stack_size;
+static pthread_attr_t stack_attr;
+static sigset_t sigusr1_set;
+
+/* Capture the range of a variable. */
+struct range_statistics
+{
+ size_t min;
+ size_t max;
+};
+
+/* Initialize a struct range_statistics object. */
+static inline struct range_statistics
+range_statistics_create (void)
+{
+ return (struct range_statistics) { .min = -1, .max = 0 };
+}
+
+/* Update the statistics object with the value. */
+static inline void
+range_statistics_update (struct range_statistics *stats, size_t value)
+{
+ if (value < stats->min)
+ stats->min = value;
+ if (value > stats->max)
+ stats->max = value;
+}
+
+/* Used to capture stack usage information. */
+static int canary_value;
+struct range_statistics untouched_low_stats;
+struct range_statistics untouched_high_stats;
+struct range_statistics touched_range_stats;
+
+
+/* Examine the stack at stack_base for traces of usage. This must be
+ called from the thread start routine because during thread
+ destruction, madvise is called on the user-supplied stack with
+ MADV_DONTNEED. */
+static void *
+capture_stack_usage (void)
+{
+ /* Computed the untouched poritions of the stack. */
+ size_t untouched_low = 0;
+ while (untouched_low < stack_size
+ && stack_base[untouched_low] == canary_value)
+ ++untouched_low;
+ size_t untouched_high = stack_size;
+ while (untouched_high > 0
+ && stack_base[untouched_high - 1] == canary_value)
+ --untouched_high;
+ TEST_VERIFY (untouched_high > untouched_low);
+
+ range_statistics_update (&untouched_low_stats, untouched_low);
+ range_statistics_update (&untouched_high_stats, untouched_high);
+ size_t used = untouched_high - untouched_low;
+ range_statistics_update (&touched_range_stats, used);
+ return NULL;
+}
+
+/* Wrapper for capture_stack_usage for use from a cancellation
+ handler. */
+static void
+capture_on_cancel (void *closure)
+{
+ capture_stack_usage ();
+}
+
+/* Attempt to determine stack usage by running THREADFUNC. Print
+ statistics and return the best estimate for the stack usage. If
+ OPERATE is not NULL, call it on the thread in question before
+ joining it. */
+static size_t
+measure_stack (const char *what, void *(*threadfunc) (void *),
+ void (*operate) (pthread_t))
+{
+ untouched_low_stats = range_statistics_create ();
+ untouched_high_stats = range_statistics_create ();
+ touched_range_stats = range_statistics_create ();
+
+ /* Use different canary values to cover usage scenarios which write
+ the same value as the canary. */
+ for (canary_value = 0; canary_value <= 255; ++canary_value)
+ {
+ memset (stack_base, canary_value, stack_size);
+ pthread_t thr = xpthread_create (&stack_attr, threadfunc, operate);
+ if (operate != NULL)
+ operate (thr);
+ xpthread_join (thr);
+ }
+
+ printf ("info: measuring stack usage, %s\n", what);
+ printf (" lowest modification offset ranges from %zu to %zu\n",
+ untouched_low_stats.min, untouched_low_stats.max);
+ printf (" highest modification offset ranges from %zu to %zu\n",
+ untouched_high_stats.min, untouched_high_stats.max);
+ printf (" minimum modified range: %zu\n", touched_range_stats.min);
+ printf (" maximum modified range: %zu\n", touched_range_stats.max);
+
+ return touched_range_stats.max;
+}
+
+static void
+noop_signal_handler (int signo)
+{
+}
+
+/* Used as the SIGUSR1 signal handler. */
+static void *
+noop_threadfunc (void *closure)
+{
+ return capture_stack_usage ();
+}
+
+/* Thread start routine for signal handler test. */
+static void *
+signal_threadfunc (void *closure)
+{
+ xpthread_sigmask (SIG_UNBLOCK, &sigusr1_set, NULL);
+ /* Delivery is synchronous, to this thread, because the signal is
+ blocked on all other threads. */
+ raise (SIGUSR1);
+ return capture_stack_usage ();
+}
+
+/* Used for synchronization in the cancellation thread. */
+static pthread_barrier_t barrier;
+
+/* Cause cancel_threadfunc to return normally. */
+static void
+operate_no_cancel (pthread_t thr)
+{
+ xpthread_barrier_wait (&barrier);
+}
+
+/* Cancel the cancel_threadfunc thread. */
+static void
+operate_cancel (pthread_t thr)
+{
+ xpthread_barrier_wait (&barrier);
+ xpthread_cancel (thr);
+}
+
+/* Thread start routine for cancellation tests. */
+static void *
+cancel_threadfunc (void *closure)
+{
+ /* Synchronization here prevents cancellation in the thread
+ initialization. */
+ xpthread_barrier_wait (&barrier);
+ pthread_cleanup_push (capture_on_cancel, NULL);
+ if (closure == operate_cancel)
+ /* Wait for cancellation. */
+ pause ();
+ pthread_cleanup_pop (1);
+ return NULL;
+}
+
+static int
+do_test (void)
+{
+ /* This should be large enough for all architectures. */
+ stack_size = 512 * 1024;
+ printf ("info: stack size: %zu\n", stack_size);
+ printf ("info: PTHREAD_STACK_MIN: %zu\n", (size_t) PTHREAD_STACK_MIN);
+ printf ("info: MINSIGSTKSZ: %zu\n", (size_t) MINSIGSTKSZ);
+ printf ("info: SIGSTKSZ: %zu\n", (size_t) SIGSTKSZ);
+ TEST_VERIFY (stack_size >= PTHREAD_STACK_MIN);
+
+ stack_base = xmmap (NULL, stack_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
+ xpthread_attr_init (&stack_attr);
+ xpthread_attr_setstack (&stack_attr, stack_base, stack_size);
+
+ /* Block the signal on all threads, so that we can force delivery on
+ a specific thread. */
+ sigemptyset (&sigusr1_set);
+ sigaddset (&sigusr1_set, SIGUSR1);
+ xpthread_sigmask (SIG_BLOCK, &sigusr1_set, NULL);
+ xsignal (SIGUSR1, noop_signal_handler);
+
+ size_t noop = measure_stack ("no signal delivery", noop_threadfunc, NULL);
+ TEST_VERIFY (noop <= PTHREAD_STACK_MIN);
+ size_t with_signal
+ = measure_stack ("with signal delivery", signal_threadfunc, NULL);
+ TEST_VERIFY (with_signal <= PTHREAD_STACK_MIN);
+ TEST_VERIFY_EXIT (with_signal >= noop);
+ printf ("info: estimated signal delivery stack overhead: %zu bytes\n",
+ with_signal - noop);
+
+ xpthread_barrier_init (&barrier, NULL, 2);
+ noop = measure_stack ("no-cancel test", cancel_threadfunc,
+ operate_no_cancel);
+ TEST_VERIFY (noop <= PTHREAD_STACK_MIN);
+ size_t with_cancel = measure_stack ("cancellation test", cancel_threadfunc,
+ operate_cancel);
+ TEST_VERIFY (with_cancel <= PTHREAD_STACK_MIN);
+ xpthread_barrier_destroy (&barrier);
+ TEST_VERIFY_EXIT (with_cancel >= noop);
+ printf ("info: estimated cancellation stack overhead: %zu bytes\n",
+ with_cancel - noop);
+
+ xpthread_attr_destroy (&stack_attr);
+ xmunmap (stack_base, stack_size);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/Makefile b/support/Makefile
index 93a5143016..c322c5efac 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -103,6 +103,7 @@ libsupport-routines = \
xpthread_attr_init \
xpthread_attr_setdetachstate \
xpthread_attr_setguardsize \
+ xpthread_attr_setstack \
xpthread_attr_setstacksize \
xpthread_barrier_destroy \
xpthread_barrier_init \
diff --git a/support/xpthread_attr_setstack.c b/support/xpthread_attr_setstack.c
new file mode 100644
index 0000000000..8b7f6fe5f2
--- /dev/null
+++ b/support/xpthread_attr_setstack.c
@@ -0,0 +1,26 @@
+/* pthread_attr_setstack with error checking.
+ Copyright (C) 2017-2018 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <support/xthread.h>
+
+void
+xpthread_attr_setstack (pthread_attr_t *attr, void *addr, size_t size)
+{
+ xpthread_check_return ("pthread_attr_setstack",
+ pthread_attr_setstack (attr, addr, size));
+}
diff --git a/support/xthread.h b/support/xthread.h
index 623f5ad0ac..209b41c2be 100644
--- a/support/xthread.h
+++ b/support/xthread.h
@@ -70,6 +70,7 @@ void xpthread_attr_setdetachstate (pthread_attr_t *attr,
int detachstate);
void xpthread_attr_setstacksize (pthread_attr_t *attr,
size_t stacksize);
+void xpthread_attr_setstack (pthread_attr_t *attr, void *, size_t);
void xpthread_attr_setguardsize (pthread_attr_t *attr,
size_t guardsize);

Loading...