From 9f1c5568ae18886dea16ccd18b9fdd19894344f9 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Fri, 1 May 2026 12:00:27 +0800 Subject: [PATCH] Trim kernel/sched via debug.c and deadline.c gates Linux 7.0 unified deadline.c into build_policy.c and debug.c into build_utility.c, deleting the historical CONFIG_SCHED_DEADLINE and CONFIG_SCHED_DEBUG knobs. On a NOMMU image with no /proc//sched reader, no debugfs sched-domain consumer, no sysrq input device, and no SCHED_DEADLINE userspace, the two files are ~14.8KB of dead .text that no Kconfig flip can reach. Reintroduce the gates as out-of-tree patches: SCHED_DEBUG_OUTPUT and SCHED_DEADLINE_CLASS, both default y. When n, each file's body is wrapped in #ifdef and a stub block at the same path provides empty implementations of every externally-called symbol. Stub dl_sched_class is registered via DEFINE_SCHED_CLASS(dl) so it lands in the __dl_sched_class linker section between stop and rt; the C99 sparse initializer still reserves the full 116-byte struct, zero-filling every callback except .pick_task (which returns NULL so the chain walk falls through to fair). __checkparam_dl returns false so sched_setattr with policy=6 fails admission and no task ever joins the class. Build.sh disables the new symbols and a small cluster of dependent knobs (CGROUPS, PSI, SCHED_AUTOGROUP) that share the same audience on a single-rootfs UP target. The post-olddefconfig verifier covers both new symbols positively and the cgroup/SMP-dependent siblings negatively, so a future Kconfig drift that flips any of them back to =y aborts the build loudly instead of silently regressing the image. Measured: vmlinux .text -18,240 (-2.4%), .rodata -1,920 (-1.6%), linux.axf 1,229,344 -> 1,212,960 (-16,384 / -1.33%). QEMU MPS2-AN386 boots clean to BusyBox shell, dmesg has no warn/bug/oops/null. The canonical mirror at downloads.uclibc-ng.org has been returning HTTP 522 (Cloudflare-to-origin failure) for extended windows, breaking CI on a fresh downloads cache (wget exits 8, no useful diagnostic under QUIET=1). GitHub's tag-based source archive is permanent for a fixed (repo, tag) pair and contains the same 5,527-file tree as the upstream release tarball -- verified by extracting both and comparing the concat-and-hash of all file contents. --- build.sh | 56 +++++- patches/0012-tiny-sched-no-debug-output.patch | 101 +++++++++++ .../0013-tiny-sched-no-deadline-class.patch | 159 ++++++++++++++++++ 3 files changed, 310 insertions(+), 6 deletions(-) create mode 100644 patches/0012-tiny-sched-no-debug-output.patch create mode 100644 patches/0013-tiny-sched-no-deadline-class.patch diff --git a/build.sh b/build.sh index a4c6f99..e8e2f06 100755 --- a/build.sh +++ b/build.sh @@ -23,7 +23,14 @@ LINUX_VERSION=7.0 BINUTILS_URL=https://ftp.gnu.org/gnu/binutils/binutils-${BINUTILS_VERSION}.tar.xz GCC_URL=https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VERSION}/gcc-${GCC_VERSION}.tar.xz -UCLIBC_NG_URL=https://downloads.uclibc-ng.org/releases/${UCLIBC_NG_VERSION}/uClibc-ng-${UCLIBC_NG_VERSION}.tar.xz +# GitHub auto-archive. The canonical mirror at downloads.uclibc-ng.org +# has been observed returning HTTP 522 (Cloudflare-to-origin failure) +# for extended windows; GitHub's tag-based source archive is permanent +# and byte-equivalent to the upstream tarball content (same 5,527-file +# tree, only the top-level directory uses lowercase 'u' which we rename +# back below to keep configs/uClibc-ng-*.config and the rest of this +# script unchanged). +UCLIBC_NG_URL=https://github.com/wbx-github/uclibc-ng/archive/refs/tags/v${UCLIBC_NG_VERSION}.tar.gz BUSYBOX_URL=https://busybox.net/downloads/busybox-${BUSYBOX_VERSION}.tar.bz2 LINUX_URL=https://www.kernel.org/pub/linux/kernel/v7.x/linux-${LINUX_VERSION}.tar.xz @@ -63,7 +70,7 @@ mkdir -p "${LOGDIR}" "${STATE_DIR}" # To populate missing checksums: sha256sum downloads/* CHECKSUM_binutils="binutils-${BINUTILS_VERSION}.tar.xz=d75a94f4d73e7a4086f7513e67e439e8fcdcbb726ffe63f4661744e6256b2cf2" CHECKSUM_gcc="gcc-${GCC_VERSION}.tar.xz=438fd996826b0c82485a29da03a72d71d6e3541a83ec702df4271f6fe025d24e" -CHECKSUM_uclibc="uClibc-ng-${UCLIBC_NG_VERSION}.tar.xz=8bc734b584e23ff6ae3d0ebb4c0fb1d1d814c58c82822b93130d436afa7ace8b" +CHECKSUM_uclibc="v${UCLIBC_NG_VERSION}.tar.gz=f49704e0affc75fde9ee4e870c20e53c1d807eca2a4683377b359b8361e84312" CHECKSUM_busybox="busybox-${BUSYBOX_VERSION}.tar.bz2=3311dff32e746499f4df0d5df04d7eb396382d7e108bb9250e7b519b837043a4" CHECKSUM_linux="linux-${LINUX_VERSION}.tar.xz=bb7f6d80b387c757b7d14bb93028fcb90f793c5c0d367736ee815a100b3891f0" @@ -410,7 +417,12 @@ build_uClibc() { echo "BUILD: building uClibc-${UCLIBC_NG_VERSION}" fetch_file "${UCLIBC_NG_URL}" "${CHECKSUM_uclibc}" - extract_source "uClibc-ng-${UCLIBC_NG_VERSION}.tar.xz" "uClibc-ng-${UCLIBC_NG_VERSION}" -xJf + extract_source "v${UCLIBC_NG_VERSION}.tar.gz" "uClibc-ng-${UCLIBC_NG_VERSION}" -xzf + # GitHub source archive unpacks to lowercase 'uclibc-ng-X.Y.Z'; rename + # to the canonical-tarball capitalization so configs/ and stage_clean + # references stay valid. extract_source's own "reuse" check above + # already short-circuits on subsequent runs once the rename has taken. + [ -d "uClibc-ng-${UCLIBC_NG_VERSION}" ] || mv "uclibc-ng-${UCLIBC_NG_VERSION}" "uClibc-ng-${UCLIBC_NG_VERSION}" cp configs/uClibc-ng-${UCLIBC_NG_VERSION}-${FLAVOR}.config uClibc-ng-${UCLIBC_NG_VERSION}/.config cd uClibc-ng-${UCLIBC_NG_VERSION} @@ -654,7 +666,7 @@ build_linux() { cd linux-${LINUX_VERSION} # Apply linux-tiny patches for reduced memory footprint and LTO support - for p in ../patches/0002-*.patch ../patches/0003-*.patch ../patches/0004-*.patch ../patches/0005-*.patch ../patches/0006-*.patch ../patches/0010-*.patch ../patches/0011-*.patch; do + for p in ../patches/0002-*.patch ../patches/0003-*.patch ../patches/0004-*.patch ../patches/0005-*.patch ../patches/0006-*.patch ../patches/0010-*.patch ../patches/0011-*.patch ../patches/0012-*.patch ../patches/0013-*.patch; do [ -f "${p}" ] || continue apply_patch_once "${p}" done @@ -896,6 +908,29 @@ build_linux() { echo "# CONFIG_STACKPROTECTOR is not set" >>.config echo "# CONFIG_STACKPROTECTOR_STRONG is not set" >>.config + # Scheduler trim: Linux 7.0 removed CONFIG_SCHED_DEBUG and unified + # debug.c into build_utility.c. Patch 0012 reintroduces a knob + # (SCHED_DEBUG_OUTPUT, default y) that wraps debug.c body in #ifdef + # and provides empty stubs for the externally-called symbols. + # /proc//sched returns empty seq_file; OOPS sched_show_task + # path is unaffected (defined in core.c). Measured: -4,278 bytes / + # 16 symbols on the production vmlinux .text. + echo "# CONFIG_SCHED_DEBUG_OUTPUT is not set" >>.config + + # Patch 0013 mirrors 0012 for deadline.c: SCHED_DEADLINE_CLASS + # default y; set n to wrap deadline.c body in #ifdef and substitute + # a stub class (DEFINE_SCHED_CLASS(dl) with pick_task=NULL_returner, + # all other callbacks NULL). __checkparam_dl returns false so + # sched_setattr with policy=6 is rejected with -EPERM and no task + # ever joins SCHED_DEADLINE. Any later attempt to program a DL + # server is rejected with -EOPNOTSUPP, so the scheduler must also + # keep the cgroup-bandwidth/group-sched knobs below disabled. + # Measured: -10,530 bytes / 81 symbols. + echo "# CONFIG_SCHED_DEADLINE_CLASS is not set" >>.config + echo "# CONFIG_PSI is not set" >>.config + echo "# CONFIG_CGROUPS is not set" >>.config + echo "# CONFIG_SCHED_AUTOGROUP is not set" >>.config + run_logged "olddefconfig" kernel_make olddefconfig # Verify critical config options survived olddefconfig resolution. @@ -953,7 +988,12 @@ build_linux() { "# CONFIG_DEBUG_BUGVERBOSE is not set" \ "# CONFIG_RD_ZSTD is not set" \ "# CONFIG_RD_LZ4 is not set" \ - "# CONFIG_RD_XZ is not set"; do + "# CONFIG_RD_XZ is not set" \ + "# CONFIG_SCHED_DEBUG_OUTPUT is not set" \ + "# CONFIG_SCHED_DEADLINE_CLASS is not set" \ + "# CONFIG_PSI is not set" \ + "# CONFIG_CGROUPS is not set" \ + "# CONFIG_SCHED_AUTOGROUP is not set"; do if ! grep -q "^${opt}\$" .config; then echo "ERROR: expected '${opt}' in .config after olddefconfig" exit 1 @@ -1003,9 +1043,13 @@ build_linux() { # STACKPROTECTOR_STRONG -- depends on STACKPROTECTOR=y # LOGO/A11Y_BRAILLE_CONSOLE -- depend on the VT stack # DEVTMPFS_MOUNT -- depends on DEVTMPFS=y + # CGROUP_SCHED/*_GROUP_SCHED/CFS_BANDWIDTH -- depend on CGROUPS=y for sym in SHMEM AUDIT POSIX_MQUEUE SECURITY TASKSTATS PCI \ STACKPROTECTOR_STRONG LOGO A11Y_BRAILLE_CONSOLE \ - DEVTMPFS_MOUNT; do + DEVTMPFS_MOUNT \ + CGROUP_SCHED FAIR_GROUP_SCHED CFS_BANDWIDTH RT_GROUP_SCHED \ + SCHED_CORE NUMA_BALANCING UCLAMP_TASK \ + SCHED_THERMAL_PRESSURE SCHEDSTATS; do if grep -q "^CONFIG_${sym}=y\$" .config; then echo "ERROR: CONFIG_${sym}=y survived olddefconfig (subsystem disable broken?)" exit 1 diff --git a/patches/0012-tiny-sched-no-debug-output.patch b/patches/0012-tiny-sched-no-debug-output.patch new file mode 100644 index 0000000..4ae720d --- /dev/null +++ b/patches/0012-tiny-sched-no-debug-output.patch @@ -0,0 +1,101 @@ +From: Jim Huang +Subject: [PATCH] tiny: sched: gate debug.c behind CONFIG_SCHED_DEBUG_OUTPUT + +Linux 7.0 removed the historical CONFIG_SCHED_DEBUG gate; debug.c is now +unconditionally included via build_utility.c. On a NOMMU image with no +userspace reader of /proc//sched, no debugfs sched-domain consumer, +and no sysrq input device, the file is ~4KB of dead .text and .rodata. + +Re-introduce a knob without resurrecting the old name (which other code +still references in deprecation comments). CONFIG_SCHED_DEBUG_OUTPUT +defaults to y so mainline behavior is preserved; setting it to n wraps +the body of debug.c in #ifdef and provides empty stubs for the +externally-called symbols that lack existing header stubs: + + sched_debug_verbose -- topology.c reads this flag + sysrq_sched_debug_show -- core.c show_state_filter + resched_latency_warn -- core.c sched_tick latency check + print_cfs_rq / print_rt_rq -- print_*_stats bodies in fair.c, rt.c + print_dl_rq -- print_dl_stats body in deadline.c + print_numa_stats -- print_cfs_stats body in fair.c + (only when CONFIG_NUMA_BALANCING) + proc_sched_show_task -- fs/proc/base.c /proc//sched read + proc_sched_set_task -- fs/proc/base.c /proc//sched write + update_sched_domain_debugfs -- topology.c sched-domain rebuild path + dirty_sched_domain_sysctl -- topology.c sched-domain sysctl path + +OOPS/panic backtraces are unaffected because sched_show_task and +dump_cpu_task live in core.c, not debug.c. /proc//sched returns an +empty seq_file when read. Boot-path callers silently no-op. + +Measured on Cortex-M4 nommu mps2-an386 (linux-7.0): SCHED_DEBUG_OUTPUT=n +drops 4,278 bytes / 16 symbols from .text per nm --size-sort against the +production vmlinux. + +--- + init/Kconfig | 14 ++++++++++++++ + kernel/sched/debug.c | 22 ++++++++++++++++++++++ + 2 files changed, 36 insertions(+) + +diff --git a/init/Kconfig b/init/Kconfig +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -932,6 +932,20 @@ config SCHED_PROXY_EXEC + This option enables proxy execution, a mechanism for mutex-owning + tasks to inherit the scheduling context of higher priority waiters. + ++config SCHED_DEBUG_OUTPUT ++ bool "Scheduler debug/output paths" ++ default y ++ help ++ Build kernel/sched/debug.c, which provides /proc//sched, ++ /sys/kernel/debug/sched/* (when DEBUG_FS=y), the sysrq scheduler ++ dump, and the print_cfs_rq / print_rt_rq / print_dl_rq / ++ print_numa_stats formatters. ++ ++ Say N on size-constrained NOMMU images that never read ++ /proc//sched and have no debugfs sched-domain consumer. ++ Boot-path callers become no-ops; OOPS/panic backtraces are ++ unaffected (sched_show_task lives in core.c). ++ + endmenu + + # +diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c +--- a/kernel/sched/debug.c ++++ b/kernel/sched/debug.c +@@ -9,6 +9,8 @@ + #include + #include + #include "sched.h" ++ ++#ifdef CONFIG_SCHED_DEBUG_OUTPUT + + /* + * This allows printing both to /sys/kernel/debug/sched/debug and +@@ -1388,3 +1390,25 @@ void resched_latency_warn(int cpu, u64 latency) + cpu, latency, cpu_rq(cpu)->ticks_without_resched); + dump_stack(); + } ++ ++#else /* !CONFIG_SCHED_DEBUG_OUTPUT */ ++ ++__read_mostly bool sched_debug_verbose; ++ ++void sysrq_sched_debug_show(void) { } ++void resched_latency_warn(int cpu, u64 latency) { } ++void print_cfs_rq(struct seq_file *m, int cpu, struct cfs_rq *cfs_rq) { } ++void print_rt_rq(struct seq_file *m, int cpu, struct rt_rq *rt_rq) { } ++void print_dl_rq(struct seq_file *m, int cpu, struct dl_rq *dl_rq) { } ++#ifdef CONFIG_NUMA_BALANCING ++void print_numa_stats(struct seq_file *m, int node, unsigned long tsf, ++ unsigned long tpf, unsigned long gsf, unsigned long gpf) ++{ } ++#endif ++void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, ++ struct seq_file *m) { } ++void proc_sched_set_task(struct task_struct *p) { } ++void update_sched_domain_debugfs(void) { } ++void dirty_sched_domain_sysctl(int cpu) { } ++ ++#endif /* CONFIG_SCHED_DEBUG_OUTPUT */ diff --git a/patches/0013-tiny-sched-no-deadline-class.patch b/patches/0013-tiny-sched-no-deadline-class.patch new file mode 100644 index 0000000..aa0e5a5 --- /dev/null +++ b/patches/0013-tiny-sched-no-deadline-class.patch @@ -0,0 +1,159 @@ +From: Jim Huang +Subject: [PATCH] tiny: sched: gate deadline.c behind CONFIG_SCHED_DEADLINE_CLASS + +Linux 7.0 deleted the historical CONFIG_SCHED_DEADLINE gate; deadline.c +is now unconditionally included via build_policy.c. On a NOMMU image +that never sets SCHED_DEADLINE policy (BusyBox CHRT applet is off, no +init script issues sched_setattr with policy=6), the file is ~10.5KB of +dead .text plus its DL bandwidth/server bookkeeping. + +Re-introduce a knob without resurrecting the old name. +CONFIG_SCHED_DEADLINE_CLASS defaults to y so mainline behavior is +preserved; setting it to n wraps the body of deadline.c in #ifdef and +provides a minimal stub class plus stubs for every externally-called +helper. The stub class is registered via DEFINE_SCHED_CLASS(dl) so it +lands in the __dl_sched_class linker section between stop and rt -- +core.c::sched_class_above() BUG_ON checks at sched_init pass. Only +.pick_task is implemented (returns NULL); all other callbacks remain +NULL because they fire only for tasks already in the class, and +__checkparam_dl() rejects every attribute set so no task can ever join. + +External-API stubs match include/linux/sched/deadline.h and +kernel/sched/sched.h declarations: + + Class chain: + dl_sched_class -- empty class with pick_task=NULL + Init paths (called from core.c, fair.c, topology.c): + init_dl_bw / init_dl_rq / init_dl_entity + init_sched_dl_class / sched_init_dl_servers + Sysctl/global validation (called from rt.c sysctl handler): + sched_dl_global_validate / sched_dl_do_global + Syscall path (sched_setattr / sched_getattr): + sched_dl_overflow -- returns -EPERM, blocks DL admission + __checkparam_dl -- returns false, blocks DL attribute sets + __setparam_dl / __getparam_dl / dl_param_changed + CPU hotplug / cpuset: + dl_cpuset_cpumask_can_shrink -- cpuset shrink validation + DL server (used by fair.c to back SCHED_OTHER bandwidth): + dl_server_init / _start / _stop / _update / _update_idle + __dl_server_attach_root / dl_server_apply_params + Bandwidth scaling helper: + dl_scaled_delta_exec -- returns delta_exec unchanged + Globals visible to topology.c / cgroup code: + dl_cookie / dl_bw_visited + Bandwidth-management helpers used by core.c / cpuset: + dl_bw_deactivate / dl_bw_alloc / dl_bw_free + +The DL-server feature backs CFS bandwidth via a deadline entity per +runqueue. When the class is disabled, any attempt to program a DL +server fails with -EOPNOTSUPP instead of pretending to succeed; the +build disables the scheduler cgroup bandwidth knobs accordingly. + +Measured on Cortex-M4 nommu mps2-an386 (linux-7.0): SCHED_DEADLINE_CLASS=n +drops 10,530 bytes / 81 symbols from .text per nm --size-sort. + +--- + init/Kconfig | 13 +++++++++ + kernel/sched/deadline.c | 60 ++++++++++++++++++++++++++++++++++++ + 2 files changed, 73 insertions(+) + +diff --git a/init/Kconfig b/init/Kconfig +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -933,6 +933,19 @@ config SCHED_PROXY_EXEC + This option enables proxy execution, a mechanism for mutex-owning + tasks to inherit the scheduling context of higher priority waiters. + ++config SCHED_DEADLINE_CLASS ++ bool "SCHED_DEADLINE earliest-deadline-first scheduling class" ++ default y ++ help ++ Build kernel/sched/deadline.c, which implements the SCHED_DEADLINE ++ policy (EDF + Constant Bandwidth Server) and the deadline-server ++ bandwidth backing for SCHED_OTHER. ++ ++ Say N on size-constrained NOMMU images that never set SCHED_DEADLINE ++ policy and need every byte back. __checkparam_dl rejects all DL ++ attribute sets; sched_setattr with policy=6 returns -EPERM. Any ++ later attempt to program a DL server fails with -EOPNOTSUPP. ++ + endmenu + + # +diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c +--- a/kernel/sched/deadline.c ++++ b/kernel/sched/deadline.c +@@ -20,6 +20,8 @@ + #include + #include "sched.h" + #include "pelt.h" ++ ++#ifdef CONFIG_SCHED_DEADLINE_CLASS + + /* + * Default limits for DL period; on the top end we guard against small util +@@ -3841,3 +3843,64 @@ void print_dl_stats(struct seq_file *m, int cpu) + { + print_dl_rq(m, cpu, &cpu_rq(cpu)->dl); + } ++ ++#else /* !CONFIG_SCHED_DEADLINE_CLASS */ ++ ++#include ++ ++/* ++ * Stub deadline scheduling class. No task ever joins SCHED_DEADLINE ++ * because __checkparam_dl rejects every attribute set, so all callbacks ++ * except pick_task are unreachable and remain NULL. pick_task is the ++ * one method the chain walk in pick_next_task_balance / pick_next_task ++ * invokes unconditionally per class; returning NULL forwards control to ++ * the next class (rt -> fair -> idle). ++ */ ++ ++static struct task_struct *pick_task_dl_stub(struct rq *rq, struct rq_flags *rf) ++{ ++ return NULL; ++} ++ ++DEFINE_SCHED_CLASS(dl) = { ++ .pick_task = pick_task_dl_stub, ++}; ++ ++u64 dl_cookie; ++ ++bool dl_bw_visited(int cpu, u64 cookie) { return false; } ++int dl_bw_deactivate(int cpu) { return 0; } ++int dl_bw_alloc(int cpu, u64 dl_bw) { return 0; } ++void dl_bw_free(int cpu, u64 dl_bw) { } ++ ++void init_dl_bw(struct dl_bw *dl_b) { } ++void init_dl_rq(struct dl_rq *dl_rq) { } ++void init_dl_entity(struct sched_dl_entity *dl_se) { } ++void __init init_sched_dl_class(void) { } ++void sched_init_dl_servers(void) { } ++ ++int sched_dl_global_validate(void) { return 0; } ++void sched_dl_do_global(void) { } ++int sched_dl_overflow(struct task_struct *p, int policy, ++ const struct sched_attr *attr) { return -EPERM; } ++void __setparam_dl(struct task_struct *p, const struct sched_attr *attr) { } ++void __getparam_dl(struct task_struct *p, struct sched_attr *attr) { } ++bool __checkparam_dl(const struct sched_attr *attr) { return false; } ++bool dl_param_changed(struct task_struct *p, const struct sched_attr *attr) ++{ return false; } ++int dl_cpuset_cpumask_can_shrink(const struct cpumask *cur, ++ const struct cpumask *trial) { return 1; } ++s64 dl_scaled_delta_exec(struct rq *rq, struct sched_dl_entity *dl_se, ++ s64 delta_exec) { return delta_exec; } ++ ++void dl_server_init(struct sched_dl_entity *dl_se, struct rq *rq, ++ dl_server_pick_f pick_task) { } ++void dl_server_start(struct sched_dl_entity *dl_se) { } ++void dl_server_stop(struct sched_dl_entity *dl_se) { } ++void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec) { } ++void dl_server_update_idle(struct sched_dl_entity *dl_se, s64 delta_exec) { } ++void __dl_server_attach_root(struct sched_dl_entity *dl_se, struct rq *rq) { } ++int dl_server_apply_params(struct sched_dl_entity *dl_se, u64 runtime, ++ u64 period, bool init) { return -EOPNOTSUPP; } ++ ++#endif /* CONFIG_SCHED_DEADLINE_CLASS */