* [PATCH] Documentation: kcov: clarify coverage example
2026-11-03 15:08 [PATCH 0/4] Documentation: Add kernel minimization guide Matthew Weber
@ 2026-11-03 15:08 ` Matthew Weber
2026-11-03 15:08 ` [PATCH 2/4] scripts: Add kernel-sloc helper script Matthew Weber
` (2 subsequent siblings)
3 siblings, 0 replies; 8+ messages in thread
From: Matthew Weber @ 2026-11-03 15:08 UTC (permalink / raw)
To: Jonathan Corbet
Cc: linux-doc, Shuah Khan, Thorsten Leemhuis, Nathan Chancellor,
Nicolas Schier, linux-kbuild, Dmitry Vyukov, Andrey Konovalov,
kasan-dev, Matthew Weber
Clarify that the saved count bounds the printed entries while KCOV
remains enabled, including during output. The examples continue to disable
KCOV after printing, as before.
Document the KCOV configuration requirements and how to symbolize
collected PCs.
Signed-off-by: Matthew Weber <matthew.l.weber@gmail.com>
---
Documentation/dev-tools/kcov.rst | 44 +++++++++++++++++++++++++++++---
1 file changed, 41 insertions(+), 3 deletions(-)
diff --git a/Documentation/dev-tools/kcov.rst b/Documentation/dev-tools/kcov.rst
index 8127849d4..0c3099c06 100644
--- a/Documentation/dev-tools/kcov.rst
+++ b/Documentation/dev-tools/kcov.rst
@@ -31,6 +31,15 @@ Collecting comparison operands is supported with GCC 8+ or with Clang.
To enable KCOV, configure the kernel with::
CONFIG_KCOV=y
+ CONFIG_DEBUG_FS=y
+
+``CONFIG_KCOV_INSTRUMENT_ALL=y`` (the default) instruments eligible kernel
+code throughout the build. If it is disabled, only explicitly selected code
+is instrumented.
+
+To resolve collected PCs to source locations, also build with debug information
+(``CONFIG_DEBUG_INFO=y``) and retain the matching ``vmlinux``. Debug information
+is not required for collection itself.
To enable comparison operands collection, set::
@@ -94,7 +103,7 @@ single syscall from within a test program:
__atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);
/* Call the target syscall call. */
read(-1, NULL, 0);
- /* Read number of PCs collected. */
+ /* Save the output count; KCOV continues recording until disabled. */
n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);
for (i = 0; i < n; i++)
printf("0x%lx\n", cover[i + 1]);
@@ -111,7 +120,36 @@ single syscall from within a test program:
return 0;
}
-After piping through ``addr2line`` the output of the program looks as follows::
+In PC mode, the first ``unsigned long`` holds the count and the remaining
+``COVER_SIZE - 1`` slots hold PCs. KCOV stops adding entries when the buffer
+is full, so increase the buffer or shorten the collection window if the count
+reaches it. An empty buffer can indicate that the target path was not
+instrumented; it is not proof that no kernel code ran.
+
+The count is read while KCOV is enabled and sets the number of entries printed.
+KCOV remains enabled while those entries are printed, so the output syscalls
+may add entries to the buffer. They are not included in the printed list,
+because the loop is bounded by the saved count. Syscall entry/return paths
+around the target can also appear.
+
+Symbolizing PCs
+~~~~~~~~~~~~~~~
+
+Save the example as ``kcov-example.c``, compile it for the target, and run it
+where debugfs is mounted and accessible::
+
+ cc -O2 -o kcov-example kcov-example.c
+ sudo ./kcov-example > kcov-pcs.txt
+ addr2line -f -i -e /path/to/matching/vmlinux < kcov-pcs.txt > kcov-symbols.txt
+
+For a cross-built target, use the matching toolchain's compiler and
+``addr2line`` (for example ``aarch64-linux-gnu-addr2line``). Use the exact build's
+debug ELF, not another build's ``vmlinux``. KCOV subtracts the kernel's KASLR
+offset on architectures that support it; do not apply an assumed relocation
+adjustment. Instrumented modules need their own debug ELF and load-address
+handling rather than symbolization against ``vmlinux``.
+
+The output can look as follows::
SyS_read
fs/read_write.c:562
@@ -184,7 +222,7 @@ Comparison operands collection is similar to coverage collection:
perror("ioctl"), exit(1);
__atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);
read(-1, NULL, 0);
- /* Read number of comparisons collected. */
+ /* Save the output count, as in PC mode. */
n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);
for (i = 0; i < n; i++) {
uint64_t ip;
--
2.39.5
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 2/4] scripts: Add kernel-sloc helper script
2026-11-03 15:08 [PATCH 0/4] Documentation: Add kernel minimization guide Matthew Weber
2026-11-03 15:08 ` [PATCH] Documentation: kcov: clarify coverage example Matthew Weber
@ 2026-11-03 15:08 ` Matthew Weber
2026-11-05 6:58 ` Nicolas Schier
2026-11-03 15:08 ` [PATCH] Documentation: admin-guide: Add kernel minimization guide Matthew Weber
2026-11-03 15:08 ` [PATCH] Documentation: admin-guide: Update workload tracing guide Matthew Weber
3 siblings, 1 reply; 8+ messages in thread
From: Matthew Weber @ 2026-11-03 15:08 UTC (permalink / raw)
To: Jonathan Corbet
Cc: linux-doc, Shuah Khan, Thorsten Leemhuis, Nathan Chancellor,
Nicolas Schier, linux-kbuild, Dmitry Vyukov, Andrey Konovalov,
kasan-dev, Matthew Weber
Add a helper script scripts/kernel-sloc to count source lines of code
(SLOC) for a configured Linux kernel build. It supports counting whole
compiled sources from compile_commands.json as well as estimating
distinct source lines represented in a vmlinux ELF using DWARF
line-table addresses.
It is intended for system integrators and application developers who
analyze a trimmed kernel, for example for safety critical applications,
and need to report how much kernel code a given configuration builds
and links. Add a MAINTAINERS entry for the script.
Signed-off-by: Matthew Weber <matthew.l.weber@gmail.com>
---
MAINTAINERS | 6 +
scripts/kernel-sloc | 299 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 305 insertions(+)
create mode 100755 scripts/kernel-sloc
diff --git a/MAINTAINERS b/MAINTAINERS
index d009e2da2..d83f37c3a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13762,6 +13762,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git
F: Documentation/dev-tools/kselftest*
F: tools/testing/selftests/
+KERNEL SLOC HELPER
+M: Matthew Weber <matthew.l.weber@gmail.com>
+L: linux-kbuild@vger.kernel.org
+S: Maintained
+F: scripts/kernel-sloc
+
KERNEL SMB3 SERVER (KSMBD)
M: Namjae Jeon <linkinjeon@kernel.org>
M: Namjae Jeon <linkinjeon@samba.org>
diff --git a/scripts/kernel-sloc b/scripts/kernel-sloc
new file mode 100755
index 000000000..ed8b787e5
--- /dev/null
+++ b/scripts/kernel-sloc
@@ -0,0 +1,299 @@
+#!/bin/sh
+# SPDX-License-Identifier: BSD-3-Clause
+#
+# kernel-sloc - Count source lines of code for a configured kernel build.
+#
+# Adapted from the Unikraft build-time SLOC reporting approach
+# (https://github.com/orgs/unikraft/discussions/1849#discussioncomment-19352529),
+# which counts compiled sources rather than the whole source tree.
+#
+# The kernel mode counts whole compiled files, including inactive #if branches,
+# but not the headers they include. The dwarf mode estimates source lines
+# represented by debug information in a linked ELF; it does not measure runtime
+# coverage. Neither count is a certification or structural-coverage result.
+
+set -eu
+
+die() {
+ echo "ERROR: $*" >&2
+ exit 1
+}
+
+need_command() {
+ command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
+}
+
+need_cloc() {
+ command -v cloc >/dev/null 2>&1 ||
+ die "required command not found: cloc (install cloc to use kernel, tree, or files mode)"
+}
+
+usage() {
+ printf 'Usage: %s [--report JSON_FILE] MODE TARGET...\n' "$0"
+ printf '%s\n' \
+ ' kernel BUILD_DIR Count whole compiled sources from compile_commands.json.' \
+ ' Requires cloc and python3. Generate the database after' \
+ ' a clean build of the configuration being measured.' \
+ ' dwarf ELF Estimate distinct source lines using DWARF addresses.' \
+ ' Requires GNU objdump, addr2line, and python3, plus' \
+ ' an ELF built with debug information (CONFIG_DEBUG_INFO).' \
+ ' Set OBJDUMP and ADDR2LINE for cross builds, or set' \
+ ' CROSS_COMPILE to the toolchain prefix.' \
+ ' tree DIR Count all C, C++, and header sources; requires cloc.' \
+ ' files FILE... Count individual source files; requires cloc.' \
+ ' --report FILE JSON inventory for kernel or dwarf; parent must exist.' \
+ ' Lists compiled files, or DWARF source_lines and functions.' \
+ ' Does not measure execution or list every linked symbol.' \
+ ' -h, --help Show this help and exit successfully.' \
+ '' \
+ 'Success prints one integer on stdout and a summary on stderr.' \
+ 'Errors exit non-zero without printing a count.' \
+ 'Counts depend on the tool versions and build options; record both.' \
+ '' \
+ 'Examples:'
+ printf ' %s --report compiled.json kernel .\n' "$0"
+ printf ' %s --report linked.json dwarf vmlinux\n' "$0"
+ printf ' %s files foo.c\n' "$0"
+ printf ' %s tree /path/to/files\n' "$0"
+ printf '%s\n' \
+ 'files omits linked libc; tree includes unused C/C++/headers, not other' \
+ 'languages. Neither is a deployed-footprint total.'
+}
+
+# Extract the code total from cloc CSV, including the empty-tree case.
+cloc_sum() {
+ awk -F',' '$2 == "SUM" {total = $5} END {print total + 0}'
+}
+
+cloc_sum_from_list() {
+ cloc --list-file="$1" --skip-uniqueness --csv --quiet >"$2" ||
+ die "cloc failed while counting source files"
+ cloc_sum <"$2"
+}
+
+# Write the report before printing the count; failed writes are not success.
+write_report() {
+ [ -n "$report" ] || return 0
+ python3 - "$report" "$mode" "$target" "$1" "$2" <<'PYREPORT'
+import json
+import os
+import sys
+import tempfile
+
+path, mode, target, count, inventory = sys.argv[1:]
+with open(inventory, encoding="utf-8") as stream:
+ data = ({"files": sorted(stream.read().splitlines())} if mode == "kernel"
+ else json.load(stream))
+data.update(mode=mode, target=os.path.abspath(target), sloc=int(count))
+protected = [target] + data["files"]
+if mode == "kernel":
+ protected.append(os.path.join(target, "compile_commands.json"))
+if os.path.realpath(path) in {os.path.realpath(p) for p in protected}:
+ sys.exit("ERROR: report must not overwrite a measured input")
+if os.path.exists(path) and any(os.path.exists(p) and os.path.samefile(path, p)
+ for p in protected):
+ sys.exit("ERROR: report must not overwrite a measured input")
+temporary = None
+try:
+ with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8",
+ dir=os.path.dirname(os.path.abspath(path)),
+ delete=False) as output:
+ temporary = output.name
+ json.dump(data, output, indent=2, sort_keys=True)
+ output.write("\n")
+ os.replace(temporary, path)
+except OSError as error:
+ sys.exit(f"ERROR: cannot write report {path}: {error}")
+finally:
+ if temporary and os.path.exists(temporary):
+ os.unlink(temporary)
+PYREPORT
+}
+
+report=""
+if [ "${1:-}" = --report ]; then
+ if [ "$#" -lt 4 ] || [ -z "$2" ]; then
+ usage >&2
+ exit 2
+ fi
+ case "$2" in
+ -*) usage >&2; exit 2 ;;
+ esac
+ report="$2"
+ shift 2
+fi
+case "${1:-}" in
+-h|--help)
+ [ "$#" -eq 1 ] && [ -z "$report" ] || { usage >&2; exit 2; }
+ usage
+ exit 0
+ ;;
+kernel|dwarf|tree)
+ [ "$#" -eq 2 ] || { usage >&2; exit 2; }
+ ;;
+files)
+ [ "$#" -ge 2 ] || { usage >&2; exit 2; }
+ ;;
+*)
+ usage >&2
+ exit 2
+ ;;
+esac
+
+mode="$1"
+target="$2"
+if [ -n "$report" ] && [ "$mode" != kernel ] && [ "$mode" != dwarf ]; then
+ die "--report supports only kernel and dwarf modes"
+fi
+
+case "$mode" in
+kernel)
+ need_cloc
+ need_command python3
+ [ -d "$target" ] || die "kernel build dir not found: $target"
+ ccj="$target/compile_commands.json"
+ if [ ! -f "$ccj" ]; then
+ # Use the source-tree generator even for an out-of-tree build.
+ script_dir="$(cd "$(dirname "$0")" && pwd)"
+ gen="$script_dir/clang-tools/gen_compile_commands.py"
+ [ -f "$gen" ] || die "no compile_commands.json or generator (build the kernel first)"
+ echo "generating compile_commands.json ..." >&2
+ python3 "$gen" -d "$target" -o "$ccj" ||
+ die "gen_compile_commands.py failed"
+ fi
+ tmpdir="$(mktemp -d)" || die "cannot create temporary directory"
+ trap 'rm -rf "$tmpdir"' 0
+ trap 'exit 1' HUP INT TERM
+ list="$tmpdir/sources"
+ python3 - "$ccj" >"$list" <<'PY'
+import json
+import os
+import sys
+
+with open(sys.argv[1], encoding="utf-8") as database:
+ commands = json.load(database)
+base = os.path.dirname(os.path.abspath(sys.argv[1]))
+seen = set()
+for entry in commands:
+ source = entry.get("file")
+ if not source:
+ continue
+ directory = os.path.join(base, entry.get("directory", ""))
+ path = os.path.realpath(os.path.join(directory, source))
+ if not os.path.isfile(path):
+ sys.exit(f"ERROR: compiled source not found: {path}")
+ if "\n" in path or "\r" in path:
+ sys.exit("ERROR: source paths must not contain newlines")
+ if path not in seen:
+ seen.add(path)
+ print(path)
+PY
+ [ -s "$list" ] || die "no compiled sources found (build the kernel first)"
+ n="$(cloc_sum_from_list "$list" "$tmpdir/cloc.csv")"
+ write_report "$n" "$list"
+ echo "kernel compiled sources: $(wc -l <"$list") files, ${n} SLOC" >&2
+ echo "$n"
+ ;;
+dwarf)
+ [ -f "$target" ] || die "ELF not found: $target (build with debug information)"
+ OBJDUMP="${OBJDUMP:-${CROSS_COMPILE:-}objdump}"
+ # Preserve the prefix when only a conventional OBJDUMP name is overridden.
+ case "$OBJDUMP" in
+ *objdump)
+ ADDR2LINE="${ADDR2LINE:-${OBJDUMP%objdump}addr2line}"
+ ;;
+ *)
+ ADDR2LINE="${ADDR2LINE:-${CROSS_COMPILE:-}addr2line}"
+ ;;
+ esac
+ need_command "$OBJDUMP"
+ need_command "$ADDR2LINE"
+ need_command python3
+ tmpdir="$(mktemp -d)" || die "cannot create temporary directory"
+ trap 'rm -rf "$tmpdir"' 0
+ trap 'exit 1' HUP INT TERM
+ inventory="$tmpdir/inventory.json"
+ # Resolve full paths before deduplicating line-table addresses.
+ LC_ALL=C "$OBJDUMP" --dwarf=decodedline "$target" >"$tmpdir/decodedline"
+ awk 'NF >= 3 && $2 ~ /^[0-9]+$/ {print $3}' "$tmpdir/decodedline" \
+ >"$tmpdir/addresses"
+ sort -u "$tmpdir/addresses" >"$tmpdir/sorted-addresses"
+ if [ -n "$report" ]; then
+ LC_ALL=C "$ADDR2LINE" -f -e "$target" <"$tmpdir/sorted-addresses" >"$tmpdir/locations"
+ else
+ LC_ALL=C "$ADDR2LINE" -e "$target" <"$tmpdir/sorted-addresses" >"$tmpdir/locations"
+ fi
+ python3 -c '
+import json
+import os
+import re
+import sys
+
+seen = set()
+functions = set()
+rows = iter(sys.stdin)
+for row in rows:
+ function = None
+ if sys.argv[1]:
+ function = row.strip()
+ row = next(rows, None)
+ if row is None:
+ sys.exit("ERROR: incomplete addr2line function/location pair")
+ match = re.fullmatch(r"(.*):([0-9]+)(?: \(discriminator [0-9]+\))?", row.strip())
+ if match and match[1] != "??" and int(match[2]) > 0:
+ file = os.path.normpath(match[1])
+ seen.add((file, int(match[2])))
+ if function and function != "??":
+ functions.add((function, file))
+if sys.argv[1]:
+ with open(sys.argv[2], "w", encoding="utf-8") as output:
+ json.dump({"files": sorted({file for file, line in seen}),
+ "source_lines": [{"file": file, "line": line}
+ for file, line in sorted(seen)],
+ "functions": [{"name": name, "file": file}
+ for name, file in sorted(functions)]}, output)
+print(len(seen))
+' "$report" "$inventory" <"$tmpdir/locations" >"$tmpdir/count"
+ n="$(cat "$tmpdir/count")"
+ [ "$n" -gt 0 ] || die "no DWARF source lines found (build with CONFIG_DEBUG_INFO)"
+ write_report "$n" "$inventory"
+ echo "dwarf distinct source lines (debug-info estimate): ${n}" >&2
+ echo "$n"
+ ;;
+tree)
+ need_cloc
+ [ -d "$target" ] || die "source dir not found: $target"
+ tmpfile="$(mktemp)" || die "cannot create temporary file"
+ trap 'rm -f "$tmpfile"' 0
+ trap 'exit 1' HUP INT TERM
+ cloc --include-lang=C,C++,'C/C++ Header' --skip-uniqueness \
+ --csv --quiet "$target" >"$tmpfile" ||
+ die "cloc failed while counting source tree"
+ n="$(cloc_sum <"$tmpfile")"
+ echo "source tree $target (C/C++ and headers): ${n} SLOC" >&2
+ echo "$n"
+ ;;
+files)
+ need_cloc
+ shift
+ newline='
+'
+ carriage_return="$(printf '\r')"
+ for source in "$@"; do
+ [ -f "$source" ] || die "source file not found: $source"
+ case "$source" in
+ *"$newline"*|*"$carriage_return"*)
+ die "source paths must not contain newlines"
+ ;;
+ esac
+ done
+ tmpdir="$(mktemp -d)" || die "cannot create temporary directory"
+ trap 'rm -rf "$tmpdir"' 0
+ trap 'exit 1' HUP INT TERM
+ list="$tmpdir/sources"
+ printf '%s\n' "$@" >"$list"
+ n="$(cloc_sum_from_list "$list" "$tmpdir/cloc.csv")"
+ echo "files: $# path(s), ${n} SLOC" >&2
+ echo "$n"
+ ;;
+esac
--
2.39.5
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH 2/4] scripts: Add kernel-sloc helper script
2026-11-03 15:08 ` [PATCH 2/4] scripts: Add kernel-sloc helper script Matthew Weber
@ 2026-11-05 6:58 ` Nicolas Schier
2026-11-05 20:05 ` Nathan Chancellor
0 siblings, 1 reply; 8+ messages in thread
From: Nicolas Schier @ 2026-11-05 6:58 UTC (permalink / raw)
To: Matthew Weber
Cc: Jonathan Corbet, linux-doc, Shuah Khan, Thorsten Leemhuis,
Nathan Chancellor, linux-kbuild, Dmitry Vyukov, Andrey Konovalov,
kasan-dev
On Tue, Nov 03, 2026 at 09:08:43AM -0600, Matthew Weber wrote:
> Add a helper script scripts/kernel-sloc to count source lines of code
> (SLOC) for a configured Linux kernel build. It supports counting whole
> compiled sources from compile_commands.json as well as estimating
> distinct source lines represented in a vmlinux ELF using DWARF
> line-table addresses.
>
> It is intended for system integrators and application developers who
> analyze a trimmed kernel, for example for safety critical applications,
> and need to report how much kernel code a given configuration builds
> and links. Add a MAINTAINERS entry for the script.
>
> Signed-off-by: Matthew Weber <matthew.l.weber@gmail.com>
> ---
> MAINTAINERS | 6 +
> scripts/kernel-sloc | 299 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 305 insertions(+)
> create mode 100755 scripts/kernel-sloc
>
Thanks for your patches.
Disclaimer: I did not really look into your patch set yet, but let me
note some major formal things ahead:
Something got heavily wrong with the patch set mails: while it looks
like this patch is number 2 from a series of 4 patches, the other
patches sent in the series are not numbered correctly. Patch management
tooling (b4) even recognises this patch set as three different versions
of a patch set, thus reviewers/maintainers cannot use that here.
Usually, 'git send-email' takes care of that in a very proper way. Did
you sent your patches one-by-one in some manual way?
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d009e2da2..d83f37c3a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13762,6 +13762,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git
> F: Documentation/dev-tools/kselftest*
> F: tools/testing/selftests/
>
> +KERNEL SLOC HELPER
> +M: Matthew Weber <matthew.l.weber@gmail.com>
> +L: linux-kbuild@vger.kernel.org
> +S: Maintained
> +F: scripts/kernel-sloc
If 'kernel-sloc' is accepted, we will have to put it to scripts/contrib/,
as 'kernel-sloc' is not part of the Linux build. 'scripts/contrib/'
does not exist yet, but we are urged to stop mixing build-related
scripts with non-build-related ones in scripts/.
> +
> KERNEL SMB3 SERVER (KSMBD)
> M: Namjae Jeon <linkinjeon@kernel.org>
> M: Namjae Jeon <linkinjeon@samba.org>
> diff --git a/scripts/kernel-sloc b/scripts/kernel-sloc
> new file mode 100755
> index 000000000..ed8b787e5
> --- /dev/null
> +++ b/scripts/kernel-sloc
> @@ -0,0 +1,299 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: BSD-3-Clause
Almost all files below scripts/ are licensed under GPL. Please consider
to switch to some GPL licensing or dual-license for consistency.
> +#
> +# kernel-sloc - Count source lines of code for a configured kernel build.
> +#
> +# Adapted from the Unikraft build-time SLOC reporting approach
> +# (https://github.com/orgs/unikraft/discussions/1849#discussioncomment-19352529),
> +# which counts compiled sources rather than the whole source tree.
> +#
> +# The kernel mode counts whole compiled files, including inactive #if branches,
> +# but not the headers they include. The dwarf mode estimates source lines
> +# represented by debug information in a linked ELF; it does not measure runtime
> +# coverage. Neither count is a certification or structural-coverage result.
> +
> +set -eu
> +
> +die() {
> + echo "ERROR: $*" >&2
> + exit 1
> +}
> +
> +need_command() {
> + command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
> +}
> +
> +need_cloc() {
> + command -v cloc >/dev/null 2>&1 ||
> + die "required command not found: cloc (install cloc to use kernel, tree, or files mode)"
> +}
> +
> +usage() {
> + printf 'Usage: %s [--report JSON_FILE] MODE TARGET...\n' "$0"
> + printf '%s\n' \
> + ' kernel BUILD_DIR Count whole compiled sources from compile_commands.json.' \
> + ' Requires cloc and python3. Generate the database after' \
> + ' a clean build of the configuration being measured.' \
> + ' dwarf ELF Estimate distinct source lines using DWARF addresses.' \
> + ' Requires GNU objdump, addr2line, and python3, plus' \
> + ' an ELF built with debug information (CONFIG_DEBUG_INFO).' \
> + ' Set OBJDUMP and ADDR2LINE for cross builds, or set' \
> + ' CROSS_COMPILE to the toolchain prefix.' \
> + ' tree DIR Count all C, C++, and header sources; requires cloc.' \
> + ' files FILE... Count individual source files; requires cloc.' \
> + ' --report FILE JSON inventory for kernel or dwarf; parent must exist.' \
> + ' Lists compiled files, or DWARF source_lines and functions.' \
> + ' Does not measure execution or list every linked symbol.' \
> + ' -h, --help Show this help and exit successfully.' \
> + '' \
> + 'Success prints one integer on stdout and a summary on stderr.' \
> + 'Errors exit non-zero without printing a count.' \
> + 'Counts depend on the tool versions and build options; record both.' \
> + '' \
> + 'Examples:'
> + printf ' %s --report compiled.json kernel .\n' "$0"
> + printf ' %s --report linked.json dwarf vmlinux\n' "$0"
> + printf ' %s files foo.c\n' "$0"
> + printf ' %s tree /path/to/files\n' "$0"
> + printf '%s\n' \
> + 'files omits linked libc; tree includes unused C/C++/headers, not other' \
> + 'languages. Neither is a deployed-footprint total.'
bike-shedding:
cat <<-eof
...
eof
might be valuable alternative to all those 'printf' calls.
> +}
> +
> +# Extract the code total from cloc CSV, including the empty-tree case.
> +cloc_sum() {
> + awk -F',' '$2 == "SUM" {total = $5} END {print total + 0}'
> +}
> +
> +cloc_sum_from_list() {
> + cloc --list-file="$1" --skip-uniqueness --csv --quiet >"$2" ||
> + die "cloc failed while counting source files"
> + cloc_sum <"$2"
> +}
> +
> +# Write the report before printing the count; failed writes are not success.
> +write_report() {
> + [ -n "$report" ] || return 0
> + python3 - "$report" "$mode" "$target" "$1" "$2" <<'PYREPORT'
Please do not embedd more than a handful of Python code lines in a shell
script. Common Python linting is simplier in a pure Python script. And
if Python is needed for the task, why is it wrapped in shell instead of
implementing the whole thing in Python?
I'd like to read your reply before reviewing this any further.
Kind regards,
Nicolas
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH 2/4] scripts: Add kernel-sloc helper script
2026-11-05 6:58 ` Nicolas Schier
@ 2026-11-05 20:05 ` Nathan Chancellor
0 siblings, 0 replies; 8+ messages in thread
From: Nathan Chancellor @ 2026-11-05 20:05 UTC (permalink / raw)
To: Nicolas Schier, Matthew Weber, Jonathan Corbet, linux-doc,
Shuah Khan, Thorsten Leemhuis, linux-kbuild, Dmitry Vyukov,
Andrey Konovalov, kasan-dev
On Thu, Nov 05, 2026 at 07:58:45AM +0100, Nicolas Schier wrote:
> On Tue, Nov 03, 2026 at 09:08:43AM -0600, Matthew Weber wrote:
> > +# Write the report before printing the count; failed writes are not success.
> > +write_report() {
> > + [ -n "$report" ] || return 0
> > + python3 - "$report" "$mode" "$target" "$1" "$2" <<'PYREPORT'
>
> Please do not embedd more than a handful of Python code lines in a shell
> script. Common Python linting is simplier in a pure Python script. And
> if Python is needed for the task, why is it wrapped in shell instead of
> implementing the whole thing in Python?
Very much agreed, this stood out when I scanned the series initially. I
think this would read much better as a full Python script, it feels like
there is very little reason for this to be a shell script with this
amount of complexity.
--
Cheers,
Nathan
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH] Documentation: admin-guide: Add kernel minimization guide
2026-11-03 15:08 [PATCH 0/4] Documentation: Add kernel minimization guide Matthew Weber
2026-11-03 15:08 ` [PATCH] Documentation: kcov: clarify coverage example Matthew Weber
2026-11-03 15:08 ` [PATCH 2/4] scripts: Add kernel-sloc helper script Matthew Weber
@ 2026-11-03 15:08 ` Matthew Weber
2026-11-04 15:09 ` sashiko-bot
2026-11-03 15:08 ` [PATCH] Documentation: admin-guide: Update workload tracing guide Matthew Weber
3 siblings, 1 reply; 8+ messages in thread
From: Matthew Weber @ 2026-11-03 15:08 UTC (permalink / raw)
To: Jonathan Corbet
Cc: linux-doc, Shuah Khan, Thorsten Leemhuis, Nathan Chancellor,
Nicolas Schier, linux-kbuild, Dmitry Vyukov, Andrey Konovalov,
kasan-dev, Matthew Weber
Add kernel-minimization.rst to the Workload analysis section of the
admin-guide, for application developers and system integrators who need
to trim a Linux kernel for a single known workload, for example when
analyzing the kernel for safety critical applications.
The guide explains how to trim a kernel for a workload running directly
as PID 1, cross-compiled for QEMU arm64 with CONFIG_MODULES=n, and how
to measure the resulting build.
Signed-off-by: Matthew Weber <matthew.l.weber@gmail.com>
---
Documentation/admin-guide/index.rst | 1 +
.../admin-guide/kernel-minimization.rst | 413 ++++++++++++++++++
.../quickly-build-trimmed-linux.rst | 4 +
MAINTAINERS | 6 +
4 files changed, 424 insertions(+)
create mode 100644 Documentation/admin-guide/kernel-minimization.rst
diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
index b734f8a2a..e16ffcd1d 100644
--- a/Documentation/admin-guide/index.rst
+++ b/Documentation/admin-guide/index.rst
@@ -178,6 +178,7 @@ subsystems expectations will be found here.
:maxdepth: 1
workload-tracing
+ kernel-minimization
Everything else
---------------
diff --git a/Documentation/admin-guide/kernel-minimization.rst b/Documentation/admin-guide/kernel-minimization.rst
new file mode 100644
index 000000000..f9bab60ac
--- /dev/null
+++ b/Documentation/admin-guide/kernel-minimization.rst
@@ -0,0 +1,413 @@
+.. SPDX-License-Identifier: (GPL-2.0+ OR CC-BY-4.0)
+
+=================================================
+Minimizing a Linux kernel for a single workload
+=================================================
+
+:Authors: - Matthew Weber <matthew.l.weber@gmail.com>
+ - ELISA Aerospace Working Group <https://lists.elisa.tech/g/aerospace>
+
+This document explains how to trim a Linux kernel for a single, known workload,
+check that it still runs, and measure the resulting build. The examples are
+starting points to adapt to your hardware and workload, not configurations to
+copy unchanged.
+
+A smaller kernel carries less code to build, boot, review, and maintain. How
+much that matters depends on the situation: a developer trimming a test kernel
+cares about build time, while an integrator preparing a kernel for analysis or
+for a safety- or security-critical deployment cares about how much code must be
+understood and justified. This guide stays with the mechanics of trimming and
+measuring; where a particular step is useful for one of those analyses, it is
+called out as an example, not as a requirement.
+
+This guide builds on, rather than repeats, several existing documents:
+
+* :doc:`quickly-build-trimmed-linux` for the mechanics of getting, building,
+ and installing a kernel. This guide assumes you have followed it at least
+ once and reuses its sections by name below -- in particular its "Download the
+ sources" and "Define the build configuration for your kernel" steps. Note
+ that guide targets a kernel built **natively for the machine you are on**; the
+ cross-compiled, boot-in-QEMU angle used here is the main thing this guide adds
+ on top of it (see `Building: native vs cross-compiled`_).
+* :doc:`workload-tracing` for discovering the subsystems a workload uses, and
+ :doc:`/trace/ftrace` for tracing the kernel functions it reaches.
+* :doc:`/dev-tools/kcov` and :doc:`/dev-tools/gcov` for coverage collection.
+* :doc:`/kbuild/kconfig` and :doc:`/kbuild/kconfig-language` for the
+ configuration mechanics, and :doc:`/kbuild/kbuild` for build variables such
+ as ``ARCH`` and ``CROSS_COMPILE``.
+
+Baseline scenario
+=================
+
+The walkthrough assumes the simplest possible launch model, which keeps the
+chain from "what the workload needs" to "what the kernel must provide" short:
+
+ **The kernel launches the workload directly as PID 1. There is no init
+ system, no service manager, and no general multi-user user space.**
+
+For the initramfs example below, the kernel runs ``/init`` by default; use
+``rdinit=<path>`` to select a different program in that archive. ``init=<path>``
+selects init after mounting a real root filesystem; it does not override a
+working initramfs ``/init``.
+
+With this baseline, the user space is essentially the application plus the C
+library it links against. The decision cascades into the configuration: no
+service management, no getty/login, only the library and kernel features the
+application itself exercises.
+
+The worked example boots an ``arm64`` kernel under QEMU with a static "hello
+world" program as PID 1. See `A minimal workload`_ below.
+
+This walkthrough assumes a monolithic build: ``CONFIG_MODULES=n`` and all
+required kernel features and drivers built in (``=y``). It does not build,
+load, measure, or account for loadable modules. With module support disabled,
+Kconfig treats tristate options as booleans, so existing ``=m`` selections
+become built-in (``=y``) rather than being dropped. A top-down build from
+``defconfig`` therefore links every ``defconfig`` module into the image; check
+the resolved configuration and disable unneeded features explicitly.
+
+Building and trimming
+=====================
+
+Change one feature group at a time, rebuild, and check that the workload still
+runs. The measurement examples below can help compare working configurations.
+
+Prerequisites
+-------------
+
+Follow "Install build requirements" in :doc:`quickly-build-trimmed-linux`
+for the native build tools. On a Debian/Ubuntu x86_64 host, this example adds::
+
+ sudo apt install gcc-aarch64-linux-gnu qemu-system-arm file
+
+The cross-compiler package brings in matching binutils and, with the normal
+recommended packages, the target libc headers and static library. ``qemu-system-arm`` provides
+``qemu-system-aarch64``; ``file`` checks the application binary. Other tools
+are introduced where they are used.
+
+Get the sources
+---------------
+
+Retrieve the kernel sources exactly as described in
+:doc:`quickly-build-trimmed-linux` ("Download the sources"). For a workload
+that must track a long-lived kernel, follow that guide's instructions for
+adding a stable/longterm branch rather than building bleeding-edge mainline, so
+you measure against a kernel with a long maintenance window. All commands below
+run from the resulting source tree.
+
+Building: native vs cross-compiled
+----------------------------------
+
+The trimmed-build guide builds a kernel **natively, for the machine you are
+running on**, and installs it there. This guide instead builds for a target
+architecture and boots the result under QEMU, so nothing is installed on your
+host. The mechanics of cross-building are not specific to minimization and are
+documented in :doc:`/kbuild/kbuild` (the ``ARCH`` and ``CROSS_COMPILE``
+variables); set them once for the worked example::
+
+ export ARCH=arm64
+ export CROSS_COMPILE=aarch64-linux-gnu-
+
+Every ``make`` below (``defconfig``, ``tinyconfig``, ``olddefconfig``,
+``Image``) then honours them. For a native arm64 build, omit ``CROSS_COMPILE`` and use the native tools.
+An x86 kernel or application cannot substitute for the arm64 images in the
+QEMU commands. Other architectures need their own image target, console,
+platform support, and emulator. The trimmed-build guide's "Install your kernel"
+and Secure Boot steps do not apply to this QEMU experiment.
+
+Build in the source tree, as in the trimmed-build guide. Save a working
+``.config`` before changing it so you can restore it if necessary. Keep the
+matching ``vmlinux`` when resolving debug information or coverage PCs.
+``make clean`` preserves ``.config`` but removes build artifacts; do not use
+``mrproper`` between changes.
+
+Iterate toward minimal
+----------------------
+
+Pick a direction. These mirror the two bases the trimmed-build guide's "Define
+the build configuration for your kernel" section describes -- starting from an
+existing (large) config versus starting from scratch:
+
+* **Top-down** -- start from a working configuration and remove features,
+ re-testing after each removal. For this arm64 QEMU example, build
+ ``defconfig`` as the baseline::
+
+ make defconfig
+ ./scripts/config -d MODULES
+ make olddefconfig
+ make clean
+ make -j"$(nproc)" Image
+
+ Follow `A minimal workload`_ to compile, pack, and boot the application.
+ Require ``hello world`` and no panic before treating this as a known-good
+ baseline (pass ``-m 512M`` or ``-m 1G`` to QEMU for defconfig images to avoid
+ exhausting the default 128 MB machine memory during driver initialization).
+ For a native target, the trimmed-build guide's
+ ``localmodconfig``/``olddefconfig`` approach is another starting point; do not
+ use the host's configuration for an unrelated cross-compiled target.
+ Save the working configuration, then change one group::
+
+ cp .config config.before
+ ./scripts/config -d IPV6
+ make olddefconfig
+ make clean
+ make -j"$(nproc)" Image
+
+* **Bottom-up** -- start from almost nothing and add only what the workload
+ needs::
+
+ make tinyconfig
+ ./scripts/config \
+ -e PRINTK -e TTY -e SERIAL_AMBA_PL011 -e SERIAL_AMBA_PL011_CONSOLE \
+ -e BINFMT_ELF -e BLK_DEV_INITRD -d MODULES
+ make olddefconfig
+ make clean
+ make -j"$(nproc)" Image
+
+ Beyond the serial options, this example needs ``PRINTK``, ``BLK_DEV_INITRD``,
+ and ``BINFMT_ELF`` for visible output, the external initramfs, and ELF
+ execution. Arm64 selects its device-tree, interrupt-controller, timer, and
+ AMBA support; those are not supplied by the
+ serial driver. This is a small bootable starting point for QEMU ``virt``,
+ not a universally minimal or production-ready configuration. Inspect the
+ resolved config: ``scripts/config`` edits requests, and unmet dependencies
+ can cause ``olddefconfig`` to discard them. Pack and boot the same PID 1
+ application as above; building ``defconfig`` first is not necessary for this
+ direction.
+
+To browse the configuration tree interactively with help text in view, use
+``make menuconfig`` (needs ``libncurses-dev``), as described in the
+trimmed-build guide's "Individual adjustments" section.
+
+After each change, repeat the boot command in `A minimal workload`_ with
+the rebuilt image and the same application archive. For this example, require
+``hello world`` and no panic. A QEMU timeout alone is not success: a failed
+boot can also hang. To inspect changes against the saved configuration::
+
+ scripts/diffconfig config.before .config > config.delta
+
+Do not assume that ``tinyconfig`` to ``defconfig`` measures one subsystem:
+optimization policy, platform drivers, and many unrelated settings change too.
+For a single-group comparison, save the last resolved config as
+``config.before``, change just that group, and clean/rebuild.
+
+Deciding what to remove
+=======================
+
+Follow :doc:`workload-tracing` for syscall and kernel-path collection. Use its
+"Tracing a minimal user space" section with the program from
+`A minimal workload`_, and its "Mapping observations to configuration" section
+for source and Kconfig analysis. Re-test each removal with the same PID 1
+workload.
+
+Measuring
+=========
+
+The following measurements can help compare builds:
+
+* **Source lines of code (SLOC)** -- a proxy for the amount of code to review.
+ Two complementary counts are available (see `Counting kernel SLOC`_): a DWARF-based
+ estimate of source lines represented in the linked image, and a ``cloc``
+ count of whole compiled source files. Neither measures runtime coverage.
+* **Image size** -- a sanity bound only, not a score; it depends on
+ compression, compiler version, and optimization level.
+* **Coverage** -- :doc:`/dev-tools/kcov` is intentionally task-scoped for
+ single-thread syscall tracing and coverage-guided fuzzing, capturing
+ only the code executed during calls on the thread that enabled tracing.
+ If the goal is structural code coverage across the entire kernel -- including
+ boot initialization, interrupt handlers, background kernel daemons, and
+ multi-process workloads -- use :doc:`/dev-tools/gcov`. GCOV provides
+ GCC/Clang compiler-level basic-block counters across the entire kernel
+ image and exports runtime coverage files through ``/sys/kernel/debug/gcov``.
+
+For meaningful comparisons, use consistent workload inputs, tool versions,
+build options, and instrumentation. These QEMU checks do not validate target
+hardware, timing, failure recovery, or a larger workload.
+
+Counting kernel SLOC
+--------------------
+
+Install ``cloc`` for compiled-source counts and ensure ``python3`` is available
+for generating the compilation database. On Debian/Ubuntu::
+
+ sudo apt install cloc python3
+
+Use ``scripts/kernel-sloc``; ``scripts/kernel-sloc --help`` lists its modes,
+requirements, and tool overrides. Successful runs print one integer on stdout
+and a human-readable summary on stderr. Errors exit non-zero without a count.
+It offers two kernel counts:
+
+* ``dwarf`` -- resolves DWARF line-table addresses in a ``vmlinux`` built with
+ ``CONFIG_DEBUG_INFO`` to distinct full-path ``file:line`` pairs using GNU
+ ``objdump`` and ``addr2line``. Optimization, inlining, and the quality of the
+ debug information affect this estimate; it is not an exact mapping of every
+ instruction to all contributing source lines.
+* ``kernel`` -- wraps ``cloc`` over the unique sources listed in
+ ``compile_commands.json``. It counts code lines in whole files, including
+ inactive preprocessor branches, but does not follow included headers. It is
+ therefore not a strict upper bound on the DWARF count.
+
+The DWARF SLOC metric needs a ``vmlinux`` built with debug information.
+Enable it before rebuilding and counting; this does not instrument execution.
+Follow the trimmed-build guide's "Debug symbols" section to enable it. Only the
+DWARF line table is needed here, so the symbol-table options that section also
+turns on (``KALLSYMS``, ``KALLSYMS_ALL``) are left out::
+
+ ./scripts/config -d DEBUG_INFO_NONE \
+ -e DEBUG_KERNEL -e DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT \
+ -d DEBUG_INFO_BTF -d DEBUG_INFO_SPLIT
+ make olddefconfig
+ make clean
+ make -j"$(nproc)" Image
+
+Check that ``CONFIG_DEBUG_INFO=y`` survives ``olddefconfig``. Reapply these
+settings after starting from ``tinyconfig``, which resets debug options, and
+rebuild before measuring. Keep this debug policy consistent across compared builds.
+
+With module support disabled, ``scripts/kernel-sloc dwarf vmlinux`` measures the
+linked kernel ELF. ``scripts/kernel-sloc kernel`` counts sources listed in its
+compilation database, including generated sources, assembly, and host helpers when
+present. The database is an inventory of compilations, not proof that every file
+is linked into the target image. Use ``make clean`` before each changed build
+and regenerate the database after building; stale ``.cmd`` files or a database
+can retain previous sources. Neither count here includes loadable modules.
+
+If the database is missing, ``scripts/kernel-sloc kernel`` runs the generator
+automatically. For an out-of-tree build, use the generator's ``-d`` option and
+pass the build directory to the counter.
+
+.. code-block:: sh
+
+ # debug-info estimate: distinct source lines represented in vmlinux
+ scripts/kernel-sloc --report linked.json dwarf vmlinux > sloc-dwarf.txt
+
+ # regenerate the database only after completing this clean build
+ python3 scripts/clang-tools/gen_compile_commands.py
+ scripts/kernel-sloc --report compiled.json kernel . > sloc-compiled.txt
+
+For a cross build, select matching tools with
+``CROSS_COMPILE=aarch64-linux-gnu- scripts/kernel-sloc dwarf vmlinux``.
+``OBJDUMP`` and ``ADDR2LINE`` override the individual tool names; if only
+``OBJDUMP`` is set to a conventional name ending in ``objdump``, the script
+uses the same prefix for ``addr2line``.
+
+The optional ``--report`` writes a JSON inventory without changing the integer
+count on stdout. ``kernel`` lists unique compiled files; ``dwarf`` lists the
+counted ``file:line`` pairs and function/file pairs resolved at those addresses.
+This is not a complete function-symbol listing; debug information, optimization,
+and inlining limit the mapping. Retain the reports to connect the built-code
+inventory to observations from :doc:`workload-tracing`.
+
+Note the SLOC and its inventories describe built code, not execution. Use
+:doc:`/dev-tools/gcov` for execution counts and :doc:`/dev-tools/kcov` for
+task-scoped kernel PCs.
+
+Measuring the image
+-------------------
+
+Image size can be tracked as a reference. It is not a minimization score on its
+own, because compression, compiler version, and optimization level all affect
+it::
+
+ make Image.gz
+ stat -c%s arch/arm64/boot/Image # raw
+ stat -c%s arch/arm64/boot/Image.gz # compressed
+
+Reducing the user space
+=======================
+
+For this small static program, the linked C library can dominate the user-space
+binary size. A different libc (for example musl) can change both size and
+syscall behavior; fewer library entry points do not automatically mean fewer
+required kernel features. Rebuild, trace, and re-test the same workload before
+accepting a library change.
+
+``scripts/kernel-sloc --help`` describes the optional application/file and
+source-tree counts, which can be used to compare user-space binaries and
+libraries.
+
+This guide focuses on the kernel and user-space components. On real systems,
+the hardware, firmware, and boot sequence can require additional kernel
+options that a QEMU run does not reveal.
+
+A minimal workload
+==================
+
+This is a "hello world" program booted as PID 1 (init). There is no init
+system, no shell, and no service manager -- the kernel execs it directly as
+``/init``.
+
+.. code-block:: c
+
+ #include <stdio.h>
+ #include <unistd.h>
+
+ int main(void)
+ {
+ printf("hello world\n");
+ fflush(stdout);
+
+ /* PID 1 must never return. Idle forever. */
+ for (;;)
+ pause();
+
+ return 0; /* not reached */
+ }
+
+Save it as ``hello_init.c`` and build it statically so the kernel can exec it
+with no dynamic loader or libraries present in the rootfs::
+
+ aarch64-linux-gnu-gcc -static -O2 -o hello_init hello_init.c
+ file hello_init # -> ELF 64-bit ... ARM aarch64 ... statically linked ...
+
+Pack the application as ``/init`` in a gzip-compressed archive.
+Install ``cpio`` if needed; ``gzip`` is normally present on the build host::
+
+ sudo apt install cpio
+ mkdir -p rootfs
+ cp hello_init rootfs/init
+ chmod 0755 rootfs/init
+ ( cd rootfs && find . | cpio -o -H newc ) | gzip > initramfs.cpio.gz
+
+With ``CONFIG_INITRAMFS_SOURCE=""``, the default built-in initramfs supplies
+``/dev/console``, so this external archive only needs ``/init``.
+
+Check the resolved configuration for built-in ``PRINTK``, ``TTY``,
+``SERIAL_AMBA_PL011``, ``SERIAL_AMBA_PL011_CONSOLE``, ``BLK_DEV_INITRD``,
+``RD_GZIP``, and ``BINFMT_ELF`` as in the bottom-up recipe. Boot the
+rebuilt image with the same application archive::
+
+ qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic \
+ -kernel arch/arm64/boot/Image \
+ -initrd initramfs.cpio.gz \
+ -append 'console=ttyAMA0'
+
+When booting larger or monolithic ``defconfig`` images, pass ``-m 512M`` or
+``-m 1G`` to provide sufficient memory for driver initialization (the default
+128 MB on QEMU ``virt`` can trigger out-of-memory panics on full configurations).
+
+There is no ``root=`` and no disk image -- the initramfs is the root and the
+kernel runs ``/init`` from it by default. The program prints ``hello world`` and
+then parks in ``pause()`` forever, so it never triggers the ``Attempted to kill
+init!`` panic. Close QEMU with ``Ctrl-a x``.
+
+References
+==========
+
+* :doc:`quickly-build-trimmed-linux` -- building and installing a kernel.
+* :doc:`workload-tracing` -- discovering subsystems used by a workload.
+* :doc:`/trace/ftrace` -- in-kernel function tracing, for seeing which kernel
+ functions a workload reaches.
+* :doc:`/dev-tools/testing-overview` -- overview of kernel testing frameworks
+ and coverage tools.
+* :doc:`/dev-tools/kcov`, :doc:`/dev-tools/gcov` -- coverage collection.
+* :doc:`/dev-tools/kselftest`, :doc:`/dev-tools/kunit/index` -- in-kernel unit
+ testing and functional test suites.
+* :doc:`/kbuild/kconfig`, :doc:`/kbuild/kconfig-language` -- configuration;
+ :doc:`/kbuild/kbuild` -- ARCH / CROSS_COMPILE and other build variables.
+* ``scripts/kernel-sloc`` -- the SLOC counter used for the metrics above.
+* ``scripts/diffconfig``, ``scripts/bloat-o-meter`` -- related size/diff
+ helpers.
+* `Kernel Size Reduction Work <https://elinux.org/Kernel_Size_Reduction_Work>`_
+ -- a 2014 list of kernel size-reduction ideas and projects, for background
+ on which options dominate a small kernel.
diff --git a/Documentation/admin-guide/quickly-build-trimmed-linux.rst b/Documentation/admin-guide/quickly-build-trimmed-linux.rst
index cb4b78468..63c9b0f4d 100644
--- a/Documentation/admin-guide/quickly-build-trimmed-linux.rst
+++ b/Documentation/admin-guide/quickly-build-trimmed-linux.rst
@@ -8,6 +8,10 @@ How to quickly build a trimmed Linux kernel
This guide explains how to swiftly build Linux kernels that are ideal for
testing purposes, but perfectly fine for day-to-day use, too.
+.. note:: To shrink a kernel down to only what one known workload needs, and
+ to measure the result, see :doc:`kernel-minimization`, which builds on this
+ guide.
+
The essence of the process (aka 'TL;DR')
========================================
diff --git a/MAINTAINERS b/MAINTAINERS
index d83f37c3a..5beb8c43f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28154,6 +28154,12 @@ F: sound/soc/codecs/arizona*
F: sound/soc/codecs/cs47l24*
F: sound/soc/codecs/wm*
+WORKLOAD ANALYSIS DOCUMENTATION
+M: Matthew Weber <matthew.l.weber@gmail.com>
+L: linux-doc@vger.kernel.org
+S: Maintained
+F: Documentation/admin-guide/kernel-minimization.rst
+
WORKQUEUE
M: Tejun Heo <tj@kernel.org>
R: Lai Jiangshan <jiangshanlai@gmail.com>
--
2.39.5
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH] Documentation: admin-guide: Add kernel minimization guide
2026-11-03 15:08 ` [PATCH] Documentation: admin-guide: Add kernel minimization guide Matthew Weber
@ 2026-11-04 15:09 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-11-04 15:09 UTC (permalink / raw)
To: Matthew Weber, Jonathan Corbet
Cc: Shuah Khan, Thorsten Leemhuis, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Dmitry Vyukov, Andrey Konovalov, kasan-dev
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] Documentation: admin-guide: kernel-minimization.rst references nonexistent scripts/kernel-sloc
- [Low] Documentation: admin-guide: kernel-minimization.rst cites nonexistent workload-tracing sections
--
commit 0e6d4d6be9381b61780fc4af63f1b4ba2d519ab3
Author: Matthew Weber <matthew.l.weber@gmail.com>
Documentation: admin-guide: Add kernel minimization guide
This commit adds Documentation/admin-guide/kernel-minimization.rst to explain
how to trim a Linux kernel for a single known workload running as PID 1 under
QEMU arm64 with CONFIG_MODULES=n, and how to measure the resulting build.
> diff --git a/Documentation/admin-guide/kernel-minimization.rst b/Documentation/admin-guide/kernel-minimization.rst
> new file mode 100644
> index 0000000000000..f9bab60ac6d2c
> --- /dev/null
> +++ b/Documentation/admin-guide/kernel-minimization.rst
> @@ -0,0 +1,413 @@
[ ... ]
> +Deciding what to remove
> +=======================
> +
> +Follow :doc:`workload-tracing` for syscall and kernel-path collection. Use its
> +"Tracing a minimal user space" section with the program from
> +`A minimal workload`_, and its "Mapping observations to configuration" section
> +for source and Kconfig analysis. Re-test each removal with the same PID 1
> +workload.
[Severity: Low]
Are the "Tracing a minimal user space" and "Mapping observations to
configuration" sections missing from Documentation/admin-guide/workload-tracing.rst,
or were those sections meant to be added in a companion patch?
Currently Documentation/admin-guide/workload-tracing.rst does not contain
either of those sections.
[ ... ]
> +Counting kernel SLOC
> +--------------------
> +
> +Install ``cloc`` for compiled-source counts and ensure ``python3`` is available
> +for generating the compilation database. On Debian/Ubuntu::
> +
> + sudo apt install cloc python3
> +
> +Use ``scripts/kernel-sloc``; ``scripts/kernel-sloc --help`` lists its modes,
> +requirements, and tool overrides. Successful runs print one integer on stdout
> +and a human-readable summary on stderr. Errors exit non-zero without a count.
[ ... ]
> +.. code-block:: sh
> +
> + # debug-info estimate: distinct source lines represented in vmlinux
> + scripts/kernel-sloc --report linked.json dwarf vmlinux > sloc-dwarf.txt
> +
> + # regenerate the database only after completing this clean build
> + python3 scripts/clang-tools/gen_compile_commands.py
> + scripts/kernel-sloc --report compiled.json kernel . > sloc-compiled.txt
[Severity: Low]
Is scripts/kernel-sloc missing from the tree?
The Counting kernel SLOC, Reducing the user space, and References sections
instruct readers to run scripts/kernel-sloc, but this script does not exist in
the repository and is not added by this patch, so those commands will fail with
a missing file error.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261103150845.13008-4-matthew.l.weber@gmail.com?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH] Documentation: admin-guide: Update workload tracing guide
2026-11-03 15:08 [PATCH 0/4] Documentation: Add kernel minimization guide Matthew Weber
` (2 preceding siblings ...)
2026-11-03 15:08 ` [PATCH] Documentation: admin-guide: Add kernel minimization guide Matthew Weber
@ 2026-11-03 15:08 ` Matthew Weber
3 siblings, 0 replies; 8+ messages in thread
From: Matthew Weber @ 2026-11-03 15:08 UTC (permalink / raw)
To: Jonathan Corbet
Cc: linux-doc, Shuah Khan, Thorsten Leemhuis, Nathan Chancellor,
Nicolas Schier, linux-kbuild, Dmitry Vyukov, Andrey Konovalov,
kasan-dev, Matthew Weber
Expand workload-tracing.rst, which is aimed at application developers
and system integrators analyzing which kernel code a workload relies on,
with guidance on ftrace function graph tracing for target binaries,
syscall trapping under QEMU with the GDB stub, and mapping workload
observations to kernel configuration symbols.
Add the file to the Workload analysis MAINTAINERS entry.
Signed-off-by: Matthew Weber <matthew.l.weber@gmail.com>
---
.../admin-guide/workload-tracing.rst | 139 +++++++++++++++++-
MAINTAINERS | 1 +
2 files changed, 133 insertions(+), 7 deletions(-)
diff --git a/Documentation/admin-guide/workload-tracing.rst b/Documentation/admin-guide/workload-tracing.rst
index 35963491b..241ee2258 100644
--- a/Documentation/admin-guide/workload-tracing.rst
+++ b/Documentation/admin-guide/workload-tracing.rst
@@ -176,6 +176,20 @@ by three workloads we have chose for this analysis.
* stress-ng
* paxtest
+To retain arguments, results, and calls from child processes, run the
+application with its normal inputs and environment::
+
+ strace -f -o syscalls.log ./app
+ strace -f -c -o syscall-summary.txt ./app
+
+These are separate runs. ``-f`` follows children and threads; ``-c`` produces
+a summary rather than the chronological trace. For a narrower investigation,
+add ``-e trace=%file`` to the first command to select file-related calls.
+``-k`` can add user-space stack traces when strace supports unwinding; it
+does not expose the kernel call stack. For an application that does not exit,
+stop the tracing session after a defined observation interval. Include
+startup and representative error inputs, not just a successful steady state.
+
What is cscope and how do we use it?
====================================
@@ -588,6 +602,116 @@ times each system call is invoked, and the corresponding Linux subsystem.
| arch_prctl | 2 | Process Mgmt. | sys_arch_prctl() |
+-------------------+-----------+-----------------+----------------------+
+Kernel paths reached by the workload
+====================================
+
+To go beyond the syscall boundary, use the function graph tracer described
+in :doc:`/trace/ftrace`. In a tracing-capable test system or QEMU guest,
+install ``trace-cmd`` and check that the running kernel provides
+``CONFIG_FTRACE``, ``CONFIG_FUNCTION_TRACER``, and
+``CONFIG_FUNCTION_GRAPH_TRACER``. Enabling ``FTRACE`` alone is not sufficient.
+Check availability and collect a task-filtered graph::
+
+ sudo trace-cmd list -t
+ sudo trace-cmd record -p function_graph -F -c -O nofuncgraph-irqs \
+ --user "$USER" -o functions.dat ./app
+ trace-cmd report -i functions.dat > functions.txt
+
+Use an isolated measurement system: these commands change its tracing state.
+``-F`` filters to the launched application, ``-c`` follows its children, and
+``--user`` avoids running the workload as root just because the recorder needs
+privileges. Preserve the workload's original user and environment. The option
+``nofuncgraph-irqs`` excludes interrupt execution from the graph; asynchronous
+work in other tasks is outside this task filter too. Check the report for lost
+events before treating missing functions as unobserved. The perf examples
+above provide sampled profiles, not an exhaustive list of executed functions.
+
+Tracing a minimal user space
+============================
+
+A single-binary initramfs has no shell or guest tracing tools. Initial discovery
+can use the same application and libc in a full guest for the target
+architecture, but that does not reproduce its launch as PID 1. The following
+inspects the actual boot with QEMU's host-side GDB stub without adding tools to
+the archive.
+
+Follow :doc:`/process/debugging/gdb-kernel-debugging` for the debug build and
+matching ``vmlinux`` setup. Add ``nokaslr`` to the guest kernel command line
+and ``-S -gdb tcp:127.0.0.1:1234`` to the QEMU boot command. ``-S`` pauses the
+guest before boot, allowing breakpoints to be installed. Connect host GDB::
+
+ gdb-multiarch /path/to/build/vmlinux
+ (gdb) target remote localhost:1234
+ (gdb) tbreak kernel_init
+ (gdb) continue
+
+For a native 64-bit arm64 workload, the following records syscall entry numbers
+and the first argument from the saved user registers. It starts after PID 1's
+kernel initialization, before the application executes::
+
+ (gdb) set pagination off
+ (gdb) set logging file pid1-syscalls.log
+ (gdb) set logging enabled on
+ (gdb) break do_el0_svc
+ (gdb) commands
+ > silent
+ > printf "syscall=%lu arg0=%#lx\n", regs->regs[8], regs->regs[0]
+ > continue
+ > end
+ (gdb) continue
+
+Interrupt GDB with Ctrl-C after the observation interval, then disable logging.
+Resolve the numbers using the matching target build's syscall-number definitions.
+These are entry observations, not decoded arguments or syscall results. To
+inspect a particular kernel path, disable the logging breakpoint, set a
+breakpoint on its implementation, and use ``bt`` or ``step`` at the hit.
+Entry functions and register layouts are architecture- and revision-specific;
+check them against the matching source. Once other processes exist, an entry
+breakpoint can also capture their calls; GDB threads in QEMU represent vCPUs,
+not Linux tasks. This is not a task-filtered whole-kernel coverage collector.
+
+QEMU also offers low-level logging with ``-d`` (list options with ``-d help``)
+and ``-D qemu.log``. For example, ``-d in_asm`` logs translated instruction
+blocks, not every execution or a workload-specific call trace. A block that is
+not logged again may still have executed again from the translation cache, so
+the log cannot show that a block did not run.
+
+Mapping observations to configuration
+-------------------------------------
+
+Use the source revision and architecture matching the measured kernel, rather
+than assuming the syscall tables above describe every build. Locate each
+syscall implementation with cscope or its ``SYSCALL_DEFINE`` declaration,
+follow calls into the relevant subsystem, and inspect its Makefile and Kconfig.
+For example, ``eventfd2`` is implemented in ``fs/eventfd.c``;
+``fs/Makefile`` builds ``eventfd.o`` under ``CONFIG_EVENTFD``. Use the
+configuration help to review its dependencies before changing the option.
+Other paths, such as the core ``write`` syscall, are built unconditionally
+and may reach different drivers depending on the file descriptor. A syscall
+does not necessarily map to one removable option.
+
+Retain results as well as names: a libc may probe an optional syscall, tolerate
+``ENOSYS``, and take a fallback path. Observing the call alone does not prove
+that the feature must remain enabled. Conversely, boot and recovery dependencies
+may be absent from an application's syscall trace. Record the observed path,
+source/configuration mapping, and result of re-testing after the change.
+
+For the distinction between built code and observed execution, use
+:doc:`/dev-tools/gcov` for execution counts and :doc:`/dev-tools/kcov` for
+task-scoped kernel PCs. Follow their collection and symbolization instructions
+in a separate measurement build; neither strace nor a sampled perf profile
+supplies those coverage results. Unobserved code remains a candidate for
+investigation, not proof of an unnecessary feature.
+
+Keep the built-code inventory alongside the runtime observations using
+``scripts/kernel-sloc --report compiled.json kernel .`` and, for the matching
+debug ELF, ``scripts/kernel-sloc --report linked.json dwarf vmlinux``. See
+:doc:`kernel-minimization` ("Counting kernel SLOC") for setup and scope. The
+reports list compiled files and DWARF-resolved functions/source lines, not
+execution. Compare full source paths as well as function names; repeated
+static names and inlining make names alone insufficient. An unobserved entry
+is a review candidate, not proof that its configuration can be removed.
+
Conclusion
==========
@@ -597,10 +721,11 @@ information on the resources in use by workloads using strace.
References
==========
- * `Discovery Linux Kernel Subsystems used by OpenAPS <https://elisa.tech/blog/2022/02/02/discovery-linux-kernel-subsystems-used-by-openaps>`_
- * `ELISA-White-Papers-Discovering Linux kernel subsystems used by a workload <https://github.com/elisa-tech/ELISA-White-Papers/blob/master/Processes/Discovering_Linux_kernel_subsystems_used_by_a_workload.md>`_
- * `strace <https://man7.org/linux/man-pages/man1/strace.1.html>`_
- * `perf <https://man7.org/linux/man-pages/man1/perf.1.html>`_
- * `paxtest README <https://github.com/opntr/paxtest-freebsd/blob/hardenedbsd/0.9.14-hbsd/README>`_
- * `stress-ng <https://www.mankier.com/1/stress-ng>`_
- * `Monitoring and managing system status and performance <https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/monitoring_and_managing_system_status_and_performance/index>`_
+ * `Discovery Linux Kernel Subsystems used by OpenAPS <https://elisa.tech/blog/2022/02/02/discovery-linux-kernel-subsystems-used-by-openaps>`__
+ * `ELISA-White-Papers-Discovering Linux kernel subsystems used by a workload <https://github.com/elisa-tech/ELISA-White-Papers/blob/master/Processes/Discovering_Linux_kernel_subsystems_used_by_a_workload.md>`__
+ * `strace <https://man7.org/linux/man-pages/man1/strace.1.html>`__
+ * `perf <https://man7.org/linux/man-pages/man1/perf.1.html>`__
+ * `paxtest README <https://github.com/opntr/paxtest-freebsd/blob/hardenedbsd/0.9.14-hbsd/README>`__
+ * `stress-ng <https://www.mankier.com/1/stress-ng>`__
+ * `Monitoring and managing system status and performance <https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/monitoring_and_managing_system_status_and_performance/index>`__
+ * :doc:`Kernel minimization <kernel-minimization>`
diff --git a/MAINTAINERS b/MAINTAINERS
index 5beb8c43f..dc3d7e9a6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28159,6 +28159,7 @@ M: Matthew Weber <matthew.l.weber@gmail.com>
L: linux-doc@vger.kernel.org
S: Maintained
F: Documentation/admin-guide/kernel-minimization.rst
+F: Documentation/admin-guide/workload-tracing.rst
WORKQUEUE
M: Tejun Heo <tj@kernel.org>
--
2.39.5
^ permalink raw reply related [flat|nested] 8+ messages in thread