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.
This commit is contained in:
Frederic Weisbecker
2025-11-30 22:20:33 +01:00
8 changed files with 219 additions and 31 deletions

View File

@@ -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)

View File

@@ -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));
}
/**

View File

@@ -103,8 +103,8 @@ static const struct kernel_param_ops lt_bind_ops = {
.get = param_get_cpumask,
};
module_param_cb(bind_readers, &lt_bind_ops, &bind_readers, 0644);
module_param_cb(bind_writers, &lt_bind_ops, &bind_writers, 0644);
module_param_cb(bind_readers, &lt_bind_ops, &bind_readers, 0444);
module_param_cb(bind_writers, &lt_bind_ops, &bind_writers, 0444);
long torture_sched_setaffinity(pid_t pid, const struct cpumask *in_mask, bool dowarn);
@@ -1211,6 +1211,10 @@ static void lock_torture_cleanup(void)
cxt.cur_ops->exit();
cxt.init_called = false;
}
free_cpumask_var(bind_readers);
free_cpumask_var(bind_writers);
torture_cleanup_end();
}

View File

@@ -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"

View File

@@ -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;

View File

@@ -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"
if test "$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/../.."
# 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

View File

@@ -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}"

View File

@@ -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