diff options
| author | Frederic Weisbecker <frederic@kernel.org> | 2025-11-30 22:20:33 +0100 |
|---|---|---|
| committer | Frederic Weisbecker <frederic@kernel.org> | 2025-11-30 22:20:33 +0100 |
| commit | 9a08942f17017b708991c5089843d4a1bfac4420 (patch) | |
| tree | ff6f10bf7f0a7bf3c77873c92d403d00b44c6e6c | |
| parent | a50413848f1cf66fa7cb464271f9f81f78808121 (diff) | |
| parent | 82a224498005b2a2e8edaba8e6b89464688f20e1 (diff) | |
Merge branch 'rcu/misc' into next
- In order to prepare the layout for nohz_full work deferral to
user exit, the context tracking state must shrink the counter
of transitions to/from RCU not watching. The only possible hazard
is to trigger wrap-around more easily, delaying a bit grace periods
when that happens. This should be a rare event though. Yet add
debugging and torture code to test that assumption.
- Fix memory leak on locktorture module
- Annotate accesses in rculist_nulls.h to prevent from KCSAN warnings.
On recent discussions, we also concluded that all those WRITE_ONCE()
and READ_ONCE() on list APIs deserve appropriate comments. Something
to be expected for the next cycle.
- Provide a script to apply several configs to several commits with torture.
- Allow torture to reuse a build directory in order to save needless
rebuild time.
- Various cleanups.
| -rw-r--r-- | include/linux/context_tracking_state.h | 44 | ||||
| -rw-r--r-- | include/linux/rculist_nulls.h | 6 | ||||
| -rw-r--r-- | kernel/locking/locktorture.c | 8 | ||||
| -rw-r--r-- | kernel/rcu/Kconfig.debug | 15 | ||||
| -rw-r--r-- | kernel/rcu/rcutorture.c | 4 | ||||
| -rwxr-xr-x | tools/testing/selftests/rcutorture/bin/kvm-again.sh | 56 | ||||
| -rwxr-xr-x | tools/testing/selftests/rcutorture/bin/kvm-series.sh | 116 | ||||
| -rw-r--r-- | tools/testing/selftests/rcutorture/configs/rcu/TREE04 | 1 |
8 files changed, 219 insertions, 31 deletions
diff --git a/include/linux/context_tracking_state.h b/include/linux/context_tracking_state.h index 7b8433d5a8ef..0b81248aa03e 100644 --- a/include/linux/context_tracking_state.h +++ b/include/linux/context_tracking_state.h @@ -18,12 +18,6 @@ enum ctx_state { CT_STATE_MAX = 4, }; -/* Odd value for watching, else even. */ -#define CT_RCU_WATCHING CT_STATE_MAX - -#define CT_STATE_MASK (CT_STATE_MAX - 1) -#define CT_RCU_WATCHING_MASK (~CT_STATE_MASK) - struct context_tracking { #ifdef CONFIG_CONTEXT_TRACKING_USER /* @@ -44,9 +38,45 @@ struct context_tracking { #endif }; +/* + * We cram two different things within the same atomic variable: + * + * CT_RCU_WATCHING_START CT_STATE_START + * | | + * v v + * MSB [ RCU watching counter ][ context_state ] LSB + * ^ ^ + * | | + * CT_RCU_WATCHING_END CT_STATE_END + * + * Bits are used from the LSB upwards, so unused bits (if any) will always be in + * upper bits of the variable. + */ #ifdef CONFIG_CONTEXT_TRACKING +#define CT_SIZE (sizeof(((struct context_tracking *)0)->state) * BITS_PER_BYTE) + +#define CT_STATE_WIDTH bits_per(CT_STATE_MAX - 1) +#define CT_STATE_START 0 +#define CT_STATE_END (CT_STATE_START + CT_STATE_WIDTH - 1) + +#define CT_RCU_WATCHING_MAX_WIDTH (CT_SIZE - CT_STATE_WIDTH) +#define CT_RCU_WATCHING_WIDTH (IS_ENABLED(CONFIG_RCU_DYNTICKS_TORTURE) ? 2 : CT_RCU_WATCHING_MAX_WIDTH) +#define CT_RCU_WATCHING_START (CT_STATE_END + 1) +#define CT_RCU_WATCHING_END (CT_RCU_WATCHING_START + CT_RCU_WATCHING_WIDTH - 1) +#define CT_RCU_WATCHING BIT(CT_RCU_WATCHING_START) + +#define CT_STATE_MASK GENMASK(CT_STATE_END, CT_STATE_START) +#define CT_RCU_WATCHING_MASK GENMASK(CT_RCU_WATCHING_END, CT_RCU_WATCHING_START) + +#define CT_UNUSED_WIDTH (CT_RCU_WATCHING_MAX_WIDTH - CT_RCU_WATCHING_WIDTH) + +static_assert(CT_STATE_WIDTH + + CT_RCU_WATCHING_WIDTH + + CT_UNUSED_WIDTH == + CT_SIZE); + DECLARE_PER_CPU(struct context_tracking, context_tracking); -#endif +#endif /* CONFIG_CONTEXT_TRACKING */ #ifdef CONFIG_CONTEXT_TRACKING_USER static __always_inline int __ct_state(void) diff --git a/include/linux/rculist_nulls.h b/include/linux/rculist_nulls.h index 89186c499dd4..d5a656cc4c6a 100644 --- a/include/linux/rculist_nulls.h +++ b/include/linux/rculist_nulls.h @@ -138,7 +138,7 @@ static inline void hlist_nulls_add_tail_rcu(struct hlist_nulls_node *n, if (last) { WRITE_ONCE(n->next, last->next); - n->pprev = &last->next; + WRITE_ONCE(n->pprev, &last->next); rcu_assign_pointer(hlist_nulls_next_rcu(last), n); } else { hlist_nulls_add_head_rcu(n, h); @@ -148,8 +148,8 @@ static inline void hlist_nulls_add_tail_rcu(struct hlist_nulls_node *n, /* after that hlist_nulls_del will work */ static inline void hlist_nulls_add_fake(struct hlist_nulls_node *n) { - n->pprev = &n->next; - n->next = (struct hlist_nulls_node *)NULLS_MARKER(NULL); + WRITE_ONCE(n->pprev, &n->next); + WRITE_ONCE(n->next, (struct hlist_nulls_node *)NULLS_MARKER(NULL)); } /** diff --git a/kernel/locking/locktorture.c b/kernel/locking/locktorture.c index ce0362f0a871..6567e5eeacc0 100644 --- a/kernel/locking/locktorture.c +++ b/kernel/locking/locktorture.c @@ -103,8 +103,8 @@ static const struct kernel_param_ops lt_bind_ops = { .get = param_get_cpumask, }; -module_param_cb(bind_readers, <_bind_ops, &bind_readers, 0644); -module_param_cb(bind_writers, <_bind_ops, &bind_writers, 0644); +module_param_cb(bind_readers, <_bind_ops, &bind_readers, 0444); +module_param_cb(bind_writers, <_bind_ops, &bind_writers, 0444); long torture_sched_setaffinity(pid_t pid, const struct cpumask *in_mask, bool dowarn); @@ -1211,6 +1211,10 @@ end: cxt.cur_ops->exit(); cxt.init_called = false; } + + free_cpumask_var(bind_readers); + free_cpumask_var(bind_writers); + torture_cleanup_end(); } diff --git a/kernel/rcu/Kconfig.debug b/kernel/rcu/Kconfig.debug index 12e4c64ebae1..625d75392647 100644 --- a/kernel/rcu/Kconfig.debug +++ b/kernel/rcu/Kconfig.debug @@ -213,4 +213,19 @@ config RCU_STRICT_GRACE_PERIOD when looking for certain types of RCU usage bugs, for example, too-short RCU read-side critical sections. + +config RCU_DYNTICKS_TORTURE + bool "Minimize RCU dynticks counter size" + depends on RCU_EXPERT && !COMPILE_TEST + default n + help + This option sets the width of the dynticks counter to its + minimum usable value. This minimum width greatly increases + the probability of flushing out bugs involving counter wrap, + but it also increases the probability of extending grace period + durations. This Kconfig option should therefore be avoided in + production due to the consequent increased probability of OOMs. + + This has no value for production and is only for testing. + endmenu # "RCU Debugging" diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c index b619c740306b..07e51974b06b 100644 --- a/kernel/rcu/rcutorture.c +++ b/kernel/rcu/rcutorture.c @@ -2438,10 +2438,8 @@ static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid) newstate = rcutorture_extend_mask(rtors.readstate, trsp); WARN_ON_ONCE(newstate & RCUTORTURE_RDR_UPDOWN); rcutorture_one_extend(&rtors.readstate, newstate, trsp, rtors.rtrsp++); - if (!rcu_torture_one_read_start(&rtors, trsp, myid)) { - rcutorture_one_extend(&rtors.readstate, 0, trsp, rtors.rtrsp); + if (!rcu_torture_one_read_start(&rtors, trsp, myid)) return false; - } rtors.rtrsp = rcutorture_loop_extend(&rtors.readstate, trsp, rtors.rtrsp); rcu_torture_one_read_end(&rtors, trsp); return true; diff --git a/tools/testing/selftests/rcutorture/bin/kvm-again.sh b/tools/testing/selftests/rcutorture/bin/kvm-again.sh index 88ca4e368489..b5239b52cb5d 100755 --- a/tools/testing/selftests/rcutorture/bin/kvm-again.sh +++ b/tools/testing/selftests/rcutorture/bin/kvm-again.sh @@ -31,7 +31,7 @@ fi if ! cp "$oldrun/scenarios" $T/scenarios.oldrun then # Later on, can reconstitute this from console.log files. - echo Prior run batches file does not exist: $oldrun/batches + echo Prior run scenarios file does not exist: $oldrun/scenarios exit 1 fi @@ -68,7 +68,7 @@ usage () { echo " --datestamp string" echo " --dryrun" echo " --duration minutes | <seconds>s | <hours>h | <days>d" - echo " --link hard|soft|copy" + echo " --link hard|soft|copy|inplace|inplace-force" echo " --remote" echo " --rundir /new/res/path" echo "Command line: $scriptname $args" @@ -121,7 +121,7 @@ do shift ;; --link) - checkarg --link "hard|soft|copy" "$#" "$2" 'hard\|soft\|copy' '^--' + checkarg --link "hard|soft|copy|inplace|inplace-force" "$#" "$2" 'hard\|soft\|copy\|inplace\|inplace-force' '^--' case "$2" in copy) arg_link="cp -R" @@ -132,6 +132,14 @@ do soft) arg_link="cp -Rs" ;; + inplace) + arg_link="inplace" + rundir="$oldrun" + ;; + inplace-force) + arg_link="inplace-force" + rundir="$oldrun" + ;; esac shift ;; @@ -172,21 +180,37 @@ fi echo ---- Re-run results directory: $rundir -# Copy old run directory tree over and adjust. -mkdir -p "`dirname "$rundir"`" -if ! $arg_link "$oldrun" "$rundir" -then - echo "Cannot copy from $oldrun to $rundir." - usage -fi -rm -f "$rundir"/*/{console.log,console.log.diags,qemu_pid,qemu-pid,qemu-retval,Warnings,kvm-test-1-run.sh.out,kvm-test-1-run-qemu.sh.out,vmlinux} "$rundir"/log -touch "$rundir/log" -echo $scriptname $args | tee -a "$rundir/log" -echo $oldrun > "$rundir/re-run" -if ! test -d "$rundir/../../bin" +if test "$oldrun" != "$rundir" then - $arg_link "$oldrun/../../bin" "$rundir/../.." + # Copy old run directory tree over and adjust. + mkdir -p "`dirname "$rundir"`" + if ! $arg_link "$oldrun" "$rundir" + then + echo "Cannot copy from $oldrun to $rundir." + usage + fi + rm -f "$rundir"/*/{console.log,console.log.diags,qemu_pid,qemu-pid,qemu-retval,Warnings,kvm-test-1-run.sh.out,kvm-test-1-run-qemu.sh.out,vmlinux} "$rundir"/log + touch "$rundir/log" + echo $scriptname $args | tee -a "$rundir/log" + echo $oldrun > "$rundir/re-run" + if ! test -d "$rundir/../../bin" + then + $arg_link "$oldrun/../../bin" "$rundir/../.." + fi +else + # Check for a run having already happened. + find "$rundir" -name console.log -print > $T/oldrun-console.log + if test -s $T/oldrun-console.log + then + echo Run already took place in $rundir + if test "$arg_link" = inplace + then + usage + fi + fi fi + +# Find runs to be done based on their qemu-cmd files. for i in $rundir/*/qemu-cmd do cp "$i" $T diff --git a/tools/testing/selftests/rcutorture/bin/kvm-series.sh b/tools/testing/selftests/rcutorture/bin/kvm-series.sh new file mode 100755 index 000000000000..2ff905a1853b --- /dev/null +++ b/tools/testing/selftests/rcutorture/bin/kvm-series.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0+ +# +# Usage: kvm-series.sh config-list commit-id-list [ kvm.sh parameters ] +# +# Tests the specified list of unadorned configs ("TREE01 SRCU-P" but not +# "CFLIST" or "3*TRACE01") and an indication of a set of commits to test, +# then runs each commit through the specified list of commits using kvm.sh. +# The runs are grouped into a -series/config/commit directory tree. +# Each run defaults to a duration of one minute. +# +# Run in top-level Linux source directory. Please note that this is in +# no way a replacement for "git bisect"!!! +# +# This script is intended to replace kvm-check-branches.sh by providing +# ease of use and faster execution. + +T="`mktemp -d ${TMPDIR-/tmp}/kvm-series.sh.XXXXXX`" +trap 'rm -rf $T' 0 + +scriptname=$0 +args="$*" + +config_list="${1}" +if test -z "${config_list}" +then + echo "$0: Need a quoted list of --config arguments for first argument." + exit 1 +fi +if test -z "${config_list}" || echo "${config_list}" | grep -q '\*' +then + echo "$0: Repetition ('*') not allowed in config list." + exit 1 +fi + +commit_list="${2}" +if test -z "${commit_list}" +then + echo "$0: Need a list of commits (e.g., HEAD^^^..) for second argument." + exit 2 +fi +git log --pretty=format:"%h" "${commit_list}" > $T/commits +ret=$? +if test "${ret}" -ne 0 +then + echo "$0: Invalid commit list ('${commit_list}')." + exit 2 +fi +sha1_list=`cat $T/commits` + +shift +shift + +RCUTORTURE="`pwd`/tools/testing/selftests/rcutorture"; export RCUTORTURE +PATH=${RCUTORTURE}/bin:$PATH; export PATH +. functions.sh + +ret=0 +nfail=0 +nsuccess=0 +faillist= +successlist= +cursha1="`git rev-parse --abbrev-ref HEAD`" +ds="`date +%Y.%m.%d-%H.%M.%S`-series" +startdate="`date`" +starttime="`get_starttime`" + +echo " --- " $scriptname $args | tee -a $T/log +echo " --- Results directory: " $ds | tee -a $T/log + +for config in ${config_list} +do + sha_n=0 + for sha in ${sha1_list} + do + sha1=${sha_n}.${sha} # Enable "sort -k1nr" to list commits in order. + echo Starting ${config}/${sha1} at `date` | tee -a $T/log + git checkout "${sha}" + time tools/testing/selftests/rcutorture/bin/kvm.sh --configs "$config" --datestamp "$ds/${config}/${sha1}" --duration 1 "$@" + curret=$? + if test "${curret}" -ne 0 + then + nfail=$((nfail+1)) + faillist="$faillist ${config}/${sha1}(${curret})" + else + nsuccess=$((nsuccess+1)) + successlist="$successlist ${config}/${sha1}" + # Successful run, so remove large files. + rm -f ${RCUTORTURE}/$ds/${config}/${sha1}/{vmlinux,bzImage,System.map,Module.symvers} + fi + if test "${ret}" -eq 0 + then + ret=${curret} + fi + sha_n=$((sha_n+1)) + done +done +git checkout "${cursha1}" + +echo ${nsuccess} SUCCESSES: | tee -a $T/log +echo ${successlist} | fmt | tee -a $T/log +echo | tee -a $T/log +echo ${nfail} FAILURES: | tee -a $T/log +echo ${faillist} | fmt | tee -a $T/log +if test -n "${faillist}" +then + echo | tee -a $T/log + echo Failures across commits: | tee -a $T/log + echo ${faillist} | tr ' ' '\012' | sed -e 's,^[^/]*/,,' -e 's/([0-9]*)//' | + sort | uniq -c | sort -k2n | tee -a $T/log +fi +echo Started at $startdate, ended at `date`, duration `get_starttime_duration $starttime`. | tee -a $T/log +echo Summary: Successes: ${nsuccess} Failures: ${nfail} | tee -a $T/log +cp $T/log tools/testing/selftests/rcutorture/res/${ds} + +exit "${ret}" diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE04 b/tools/testing/selftests/rcutorture/configs/rcu/TREE04 index dc4985064b3a..67caf4276bb0 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE04 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE04 @@ -16,3 +16,4 @@ CONFIG_DEBUG_OBJECTS_RCU_HEAD=n CONFIG_RCU_EXPERT=y CONFIG_RCU_EQS_DEBUG=y CONFIG_RCU_LAZY=y +CONFIG_RCU_DYNTICKS_TORTURE=y |