CPU governor and thermal stability#

Why#

By default, Linux dynamically adjusts CPU core frequency based on load, to save power (the ondemand or schedutil governor). Every frequency change introduces transition latency and a temperature swing, both unwanted sources of jitter for a system aiming for time precision. The frequency is therefore locked to maximum, permanently.

How#

Locking to maximum frequency#

cat > /etc/systemd/system/cpu-governor.service <<'EOF'
[Unit]
Description=CPU frequency governor: performance (Stratum 1)
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=basic.target

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'for c in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo performance > "$c"; done'
RemainAfterExit=true

[Install]
WantedBy=basic.target
EOF

systemctl daemon-reload
systemctl enable --now cpu-governor.service

Confining the OS to core 0#

On top of the isolcpus=2,3 isolation from the Firmware chapter, all generic services are explicitly confined to core 0, critical services (chrony, phc2sys) will redefine their own affinity to core 1.

mkdir -p /etc/systemd/system.conf.d/
cat > /etc/systemd/system.conf.d/00-stratum1-affinity.conf <<'EOF'
[Manager]
CPUAffinity=0
EOF

systemctl daemon-reexec

Pitfalls to avoid#

  • Using DefaultCPUAffinity= instead of CPUAffinity=. This directive doesn’t exist in systemd, a common confusion with other Default*= options. Only CPUAffinity= under [Manager] actually works (see man systemd-system.conf).
  • Expecting daemon-reexec to immediately change PID 1’s affinity. systemd’s own process (PID 1) has its CPU mask fixed at launch time by the kernel, daemon-reexec reloads the config for new services launched afterward, but doesn’t retroactively change the affinity already allocated to PID 1. A full reboot is needed to see the change applied to PID 1 itself.
  • Looking for cpufrequtils. This package has been dropped from recent Debian repositories. The systemd-service-writing-directly-to-sysfs approach above depends on no third-party package.

Verify#

# "performance" governor on all 4 cores
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# Effective frequency at maximum (2.4 GHz on Pi 5)
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq

# PID 1's affinity (before reboot: reflects isolcpus alone;
# after reboot: reflects isolcpus + CPUAffinity=0 combined)
grep Cpus_allowed_list /proc/1/status

# Indirect test: a service launched NOW inherits the new default
systemd-run --pipe --wait sh -c 'grep Cpus_allowed_list /proc/self/status'
# Should return "Cpus_allowed_list: 0"

systemctl show --property=CPUAffinity returns empty at the manager level, this property is only exposed for individual units. The reliable test goes through /proc/self/status via systemd-run, as shown above.

Actual thermal stability (active cooling, measuring the impact on oscillator drift) is covered separately in the Active thermal stabilization chapter.