From 0d6526f82c3cdefcca47f73f5fc08dc6f335eac6 Mon Sep 17 00:00:00 2001 From: Tim Chen Date: Mon, 21 Sep 2026 17:37:22 -0700 Subject: sched/cache: Keep nr_pref_llc_running in the runnable domain, to fix LLC mis-scheduling bug alb_break_llc() decides whether to break LLC preference during active load balance. It does so by testing that every runnable fair task on the source rq prefers its LLC: env->src_rq->nr_pref_llc_running == env->src_rq->cfs.h_nr_runnable But the two counters cover different sets. nr_pref_llc_running is updated in account_llc_enqueue()/account_llc_dequeue(), next to cfs_rq->nr_queued, so it follows queued tasks. h_nr_runnable is updated in set_delayed()/ clear_delayed() and drops delay-dequeued tasks. So under DELAY_DEQUEUE, a preferring task that goes to sleep stays counted in nr_pref_llc_running while h_nr_runnable falls. The equality then breaks, alb_break_llc() returns false, and active balance is free to pull a task off its preferred LLC. Active balance only moves runnable tasks, and this is the only LLC check it consults: once the stopper runs, LBF_ACTIVE_LB skips the per-task test in can_migrate_task(). The runnable set is the one we want. Fix it on the counter side. A task should be counted in nr_pref_llc_running exactly while it is both queued on its preferred LLC (pref_llc_queued) and runnable (!sched_delayed). Define that membership once in task_pref_llc_runnable(), and adjust the counter only through pref_llc_running_inc()/pref_llc_running_dec() from the four sites that change either input: account_llc_enqueue(), account_llc_dequeue(), set_delayed() and clear_delayed(). Gating every update on the same predicate keeps the delay, wake and dequeue paths from double-counting or underflowing; see the comments at those sites for the ordering. nr_llc_running and sd->llc_counts are not touched and stay on queued semantics. Fixes: 714059f79ff0 ("sched/cache: Handle moving single tasks to/from their preferred LLC") Closes: https://lore.kernel.org/lkml/20260827135000.735138-1-zhanxusheng@xiaomi.com/ Reported-by: Zhan Xusheng Suggested-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Kayra Cizmeci Cc: # v7.2.x Link: https://patch.msgid.link/06af61afedac32e6477f57feb4d658f6c411c3af.1790035273.git.tim.c.chen@linux.intel.com --- scripts/check-sysctl-docs | 174 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100755 scripts/check-sysctl-docs (limited to 'scripts/check-sysctl-docs') diff --git a/scripts/check-sysctl-docs b/scripts/check-sysctl-docs new file mode 100755 index 000000000..910fd8a9a --- /dev/null +++ b/scripts/check-sysctl-docs @@ -0,0 +1,174 @@ +#!/usr/bin/env -S gawk -f +# SPDX-License-Identifier: GPL-2.0 + +# Script to check sysctl documentation against source files +# +# Copyright (c) 2020 Stephen Kitt + +# Example invocation: +# scripts/check-sysctl-docs -vtable="kernel" \ +# Documentation/admin-guide/sysctl/kernel.rst \ +# $(git grep -l register_sysctl) +# +# Specify -vdebug=1 to see debugging information + +BEGIN { + if (!table) { + print "Please specify the table to look for using the table variable" > "/dev/stderr" + exit 1 + } + + # Documentation title skiplist + skiplist[0] = "^Documentation for" + skiplist[1] = "Network core options$" + skiplist[2] = "POSIX message queues filesystem$" + skiplist[3] = "Configuration options" + skiplist[4] = ". /proc/sys/fs" + skiplist[5] = "^Introduction$" + skiplist[6] = "^seccomp$" + skiplist[7] = "^pty$" + skiplist[8] = "^firmware_config$" + skiplist[9] = "^random$" +} + +# The following globals are used: +# documented: maps documented entries (each key is an entry) +# entries: maps ctl_table names and procnames to counts (so +# enumerating the subkeys for a given ctl_table lists its +# procnames) +# curtable: the name of the current ctl_table struct +# curentry: the name of the current proc entry (procname when parsing +# a ctl_table, constructed path when parsing a ctl_path) + + +# Remove punctuation from the given value +function trimpunct(value) { + while (value ~ /^["&]/) { + value = substr(value, 2) + } + while (value ~ /[]["&,}]$/) { + value = substr(value, 1, length(value) - 1) + } + return value +} + +# Print the information for the given entry +function printentry(entry) { + seen[entry]++ + printf "* %s from %s", entry, file[entry] + if (documented[entry]) { + printf " (documented)" + } + print "" +} + + +# Stage 1: build the list of documented entries +FNR == NR && /^=+$/ { + for (i in skiplist) { + if (prevline ~ skiplist[i]) { + next + } + } + + # The previous line is a section title, parse it + $0 = prevline + if (debug) print "Parsing " $0 + inbrackets = 0 + for (i = 1; i <= NF; i++) { + if (length($i) == 0) { + continue + } + if (!inbrackets && substr($i, 1, 1) == "(") { + inbrackets = 1 + } + if (!inbrackets) { + token = trimpunct($i) + if (length(token) > 0 && token != "and") { + if (debug) print trimpunct($i) + documented[trimpunct($i)]++ + } + } + if (inbrackets && substr($i, length($i), 1) == ")") { + inbrackets = 0 + } + } +} + +FNR == NR { + prevline = $0 + next +} + + +# Stage 2: process each file and find all sysctl tables +BEGINFILE { + delete entries + curtable = "" + curentry = "" + delete vars + if (debug) print "Processing file " FILENAME +} + +/^static( const)? struct ctl_table/ { + match($0, /static( const)? struct ctl_table ([^][]+)/, tables) + curtable = tables[2] + if (debug) print "Processing table " curtable +} + +/^};$/ { + curtable = "" + curentry = "" + delete vars +} + +curtable && /\.procname[\t ]*=[\t ]*".+"/ { + match($0, /.procname[\t ]*=[\t ]*"([^"]+)"/, names) + curentry = names[1] + if (debug) print "Adding entry " curentry " to table " curtable + entries[curtable][curentry]++ + file[curentry] = FILENAME +} + +curtable && /UCOUNT_ENTRY.*/ { + match($0, /UCOUNT_ENTRY\("([^"]+)"\)/, names) + curentry = names[1] + if (debug) print "Adding entry " curentry " to table " curtable + entries[curtable][curentry]++ + file[curentry] = FILENAME +} + +/register_sysctl.*/ { + match($0, /register_sysctl(|_init|_sz)\("([^"]+)" *, *([^,)]+)/, tables) + if (debug) print "Registering table " tables[3] " at " tables[2] + if (tables[2] == table) { + for (entry in entries[tables[3]]) { + printentry(entry) + } + } +} + +/kmemdup.*/ { + match($0, /([^ \t]+) *= *kmemdup\(([^,]+) *,/, names) + if (debug) print "Found variable " names[1] " for table " names[2] + if (names[2] in entries) { + vars[names[1]] = names[2] + } +} + +/__register_sysctl_table.*/ { + match($0, /__register_sysctl_table\([^,]+, *"([^"]+)" *, *([^,]+)/, tables) + if (debug) print "Registering variable table " tables[2] " at " tables[1] + if (tables[1] == table && tables[2] in vars) { + for (entry in entries[vars[tables[2]]]) { + printentry(entry) + } + } +} + +END { + for (entry in documented) { + if (!seen[entry]) + print "No implementation for " entry + } +} -- cgit v1.3.1