From 8a9c90e696a7f0e68e04bce1447091850fe46ca1 Mon Sep 17 00:00:00 2001 From: LuoChen Date: Sat, 13 Dec 2025 22:36:00 +0800 Subject: [PATCH 1/3] nixos/zswap: init module --- nixos/modules/module-list.nix | 1 + nixos/modules/system/boot/zswap.nix | 170 ++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 nixos/modules/system/boot/zswap.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 430872242d57..63bda8834a70 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1895,6 +1895,7 @@ ./system/boot/unl0kr.nix ./system/boot/uvesafb.nix ./system/boot/zram-as-tmp.nix + ./system/boot/zswap.nix ./system/etc/etc-activation.nix ./system/service/systemd/system.nix ./system/service/systemd/user.nix diff --git a/nixos/modules/system/boot/zswap.nix b/nixos/modules/system/boot/zswap.nix new file mode 100644 index 000000000000..827539baef6e --- /dev/null +++ b/nixos/modules/system/boot/zswap.nix @@ -0,0 +1,170 @@ +# Zswap - Compressed Cache for Swap Pages +# +# Reference documentation: +# - https://docs.kernel.org/admin-guide/mm/zswap.html +# - https://www.kernel.org/doc/html/v6.1/admin-guide/mm/zswap.html +# +# IMPORTANT: When modifying this file, ensure that activationScripts implementation +# remains consistent with config implementation (kernel parameters and sysfs settings) + +{ config, lib, ... }: + +with lib; + +let + cfg = config.boot.zswap; + + # Get the current configured kernel version string + kernelVersion = config.boot.kernelPackages.kernel.version; + + # Check if kernel supports zsmalloc as zswap backend (>= 6.3) + zsmallocSupported = versionAtLeast kernelVersion "6.3"; +in +{ + options.boot.zswap = { + enable = mkEnableOption "Zswap (Compressed Cache for Swap Pages)"; + + compressor = mkOption { + type = types.enum [ + "zstd" + "lz4" + "lzo" + "lz4hc" + "deflate" + "842" + ]; + default = "zstd"; + description = '' + Compression algorithm to use for zswap. + + Available options: + - 'zstd': Best compression ratio, excellent for Nix builds (default) + - 'lz4': Fastest compression, lowest latency + - 'lz4hc': High-compression variant of lz4, slower but better ratio + - 'lzo': Good balance of speed and compression (kernel default) + - 'deflate': Higher compression, slower processing + - '842': Hardware-accelerated compression on supported systems + + Note: The chosen algorithm must be supported by your kernel configuration. + ''; + }; + + zpool = mkOption { + type = types.enum [ + "zsmalloc" + "zbud" + ]; + default = if zsmallocSupported then "zsmalloc" else "zbud"; + defaultText = literalExpression "if kernel >= 6.3 then \"zsmalloc\" else \"zbud\""; + description = '' + Kernel zpool allocator. + 'zsmalloc' is strongly recommended for kernels >= 6.3 as it offers the best density. + For older kernels, 'zbud' is the fallback. + + Note: 'z3fold' was removed from Linux kernel 6.8 and later. + ''; + }; + + maxPoolPercent = mkOption { + type = types.ints.between 1 100; + default = 25; + description = '' + The maximum percentage of system memory that Zswap can occupy (1-100). + + Higher values provide more compression cache but increase memory pressure. + Default is 25% (higher than kernel default of 20%) for better Nix build performance. + + Recommended ranges: + - Desktop systems: 15-25% + - Low-memory systems: 30-50% + - Server systems: 10-20% + ''; + }; + + acceptThresholdPercent = mkOption { + type = types.ints.between 1 100; + default = 90; + description = '' + Threshold percentage at which zswap starts accepting pages again after the pool becomes full (1-100). + + This parameter provides hysteresis to prevent pool oscillation. + When the pool usage drops below this threshold, zswap starts accepting new pages. + Default is 90% as recommended by kernel documentation. + ''; + }; + + shrinkerEnabled = mkOption { + type = types.bool; + default = true; + description = '' + Enable the zswap shrinker to reclaim memory when under pressure. + + When enabled, the shrinker will automatically reclaim compressed pages + from the zswap pool when the system is under memory pressure, helping + to prevent out-of-memory situations. + + It is recommended to keep this enabled for most workloads, especially + on systems with limited memory. + ''; + }; + }; + + config = mkIf cfg.enable { + # 1. Core configuration: kernel parameters + boot.kernelParams = [ + "zswap.enabled=1" + "zswap.compressor=${cfg.compressor}" + "zswap.zpool=${cfg.zpool}" + "zswap.max_pool_percent=${toString cfg.maxPoolPercent}" + "zswap.accept_threshold_percent=${toString cfg.acceptThresholdPercent}" + "zswap.shrinker_enabled=${if cfg.shrinkerEnabled then "1" else "0"}" + ]; + + # 2. Dependency management: ensure required modules are included in initrd or kernel + # This ensures Zswap is ready early in the boot process (before swap is mounted) + boot.initrd.kernelModules = [ + cfg.compressor + cfg.zpool + ]; + + assertions = [ + { + assertion = !config.zramSwap.enable; + message = '' + Conflicting options enabled: 'boot.zswap.enable' and 'zramSwap.enable'. + + You cannot enable Zswap and Zram simultaneously as it leads to double compression + and inefficient memory management. + + Please disable one of them: + - To use Zswap (requires a physical swap device): Set 'zramSwap.enable = false'. + - To use Zram (swap in RAM): Set 'boot.zswap.enable = false'. + ''; + } + { + assertion = config.swapDevices != [ ]; + message = '' + Zswap requires at least one physical swap device to function as a backing store. + + Try adding the following to your configuration (example): + + swapDevices = [ { + device = "/var/lib/swapfile"; + size = 16 * 1024; # 16GB + } ]; + ''; + } + { + assertion = (cfg.zpool == "zsmalloc") -> zsmallocSupported; + message = '' + Zswap allocator 'zsmalloc' is not supported on kernel version ${kernelVersion}. + Support for zsmalloc in Zswap was added in Linux 6.3. + + Please use 'zbud' instead: boot.zswap.zpool = "zbud"; + ''; + } + ]; + }; + + meta.maintainers = with lib.maintainers; [ luochen1990 ]; +} From c1870198aa5bd254efc932b11aa6d1475a195275 Mon Sep 17 00:00:00 2001 From: LuoChen Date: Sat, 13 Dec 2025 22:36:09 +0800 Subject: [PATCH 2/3] nixos/zswap: add activationScripts --- nixos/modules/system/boot/zswap.nix | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/nixos/modules/system/boot/zswap.nix b/nixos/modules/system/boot/zswap.nix index 827539baef6e..e69a71a658b5 100644 --- a/nixos/modules/system/boot/zswap.nix +++ b/nixos/modules/system/boot/zswap.nix @@ -127,6 +127,58 @@ in cfg.zpool ]; + # 3. Activation scripts for runtime configuration and rebuild handling + system.activationScripts.zswap-stop = { + supportsDryActivation = true; + deps = [ "specialfs" ]; + text = '' + # Stop zswap during system rebuild to prevent state conflicts + # This script runs before the new configuration is activated + if [ -f /sys/module/zswap/parameters/enabled ]; then + current_enabled=$(cat /sys/module/zswap/parameters/enabled) + if [ "$current_enabled" = "Y" ] || [ "$current_enabled" = "1" ]; then + echo "Stopping zswap for system rebuild..." + # Disable zswap to clear any existing state + echo N > /sys/module/zswap/parameters/enabled 2>/dev/null || true + # Wait a moment for the disable to take effect + sleep 1 + fi + fi + ''; + }; + + system.activationScripts.zswap-start = { + supportsDryActivation = true; + deps = [ + "zswap-stop" + "filesystems" + ]; + text = '' + # Start zswap with the configured parameters + # This script runs after the new configuration is activated + if [ -f /sys/module/zswap/parameters/enabled ]; then + echo "Starting zswap with configured parameters..." + + # Configure zswap parameters via sysfs + # Note: These may already be set via kernel parameters, but we set them + # here to ensure they're correct even if the kernel parameters change + echo ${cfg.compressor} > /sys/module/zswap/parameters/compressor 2>/dev/null || true + echo ${cfg.zpool} > /sys/module/zswap/parameters/zpool 2>/dev/null || true + echo ${toString cfg.maxPoolPercent} > /sys/module/zswap/parameters/max_pool_percent 2>/dev/null || true + echo ${toString cfg.acceptThresholdPercent} > /sys/module/zswap/parameters/accept_threshold_percent 2>/dev/null || true + echo Y > /sys/module/zswap/parameters/same_filled_pages_enabled 2>/dev/null || true + echo Y > /sys/module/zswap/parameters/writeback 2>/dev/null || true + + # Enable zswap + echo Y > /sys/module/zswap/parameters/enabled 2>/dev/null || true + + echo "Zswap enabled with compressor=${cfg.compressor}, zpool=${cfg.zpool}, max_pool_percent=${toString cfg.maxPoolPercent}%, accept_threshold_percent=${toString cfg.acceptThresholdPercent}%" + else + echo "Warning: zswap sysfs interface not available. Check kernel configuration." + fi + ''; + }; + assertions = [ { assertion = !config.zramSwap.enable; From 9bcddd22fd5454aaa97345975837518686dd658c Mon Sep 17 00:00:00 2001 From: LuoChen Date: Sat, 13 Dec 2025 22:36:25 +0800 Subject: [PATCH 3/3] nixos/zswap: use boot.kernel.sysfs to simplify dynamic reload --- nixos/modules/system/boot/zswap.nix | 66 ++++++----------------------- 1 file changed, 13 insertions(+), 53 deletions(-) diff --git a/nixos/modules/system/boot/zswap.nix b/nixos/modules/system/boot/zswap.nix index e69a71a658b5..bc5c3d97159a 100644 --- a/nixos/modules/system/boot/zswap.nix +++ b/nixos/modules/system/boot/zswap.nix @@ -4,8 +4,9 @@ # - https://docs.kernel.org/admin-guide/mm/zswap.html # - https://www.kernel.org/doc/html/v6.1/admin-guide/mm/zswap.html # -# IMPORTANT: When modifying this file, ensure that activationScripts implementation -# remains consistent with config implementation (kernel parameters and sysfs settings) +# IMPORTANT: When modifying this file, ensure that boot.kernel.sysfs configuration +# remains consistent with kernel parameters configuration. The sysfs settings provide +# runtime management while kernel parameters ensure early-boot availability. { config, lib, ... }: @@ -110,7 +111,7 @@ in }; config = mkIf cfg.enable { - # 1. Core configuration: kernel parameters + # 1. Core configuration: kernel parameters for early boot boot.kernelParams = [ "zswap.enabled=1" "zswap.compressor=${cfg.compressor}" @@ -127,56 +128,15 @@ in cfg.zpool ]; - # 3. Activation scripts for runtime configuration and rebuild handling - system.activationScripts.zswap-stop = { - supportsDryActivation = true; - deps = [ "specialfs" ]; - text = '' - # Stop zswap during system rebuild to prevent state conflicts - # This script runs before the new configuration is activated - if [ -f /sys/module/zswap/parameters/enabled ]; then - current_enabled=$(cat /sys/module/zswap/parameters/enabled) - if [ "$current_enabled" = "Y" ] || [ "$current_enabled" = "1" ]; then - echo "Stopping zswap for system rebuild..." - # Disable zswap to clear any existing state - echo N > /sys/module/zswap/parameters/enabled 2>/dev/null || true - # Wait a moment for the disable to take effect - sleep 1 - fi - fi - ''; - }; - - system.activationScripts.zswap-start = { - supportsDryActivation = true; - deps = [ - "zswap-stop" - "filesystems" - ]; - text = '' - # Start zswap with the configured parameters - # This script runs after the new configuration is activated - if [ -f /sys/module/zswap/parameters/enabled ]; then - echo "Starting zswap with configured parameters..." - - # Configure zswap parameters via sysfs - # Note: These may already be set via kernel parameters, but we set them - # here to ensure they're correct even if the kernel parameters change - echo ${cfg.compressor} > /sys/module/zswap/parameters/compressor 2>/dev/null || true - echo ${cfg.zpool} > /sys/module/zswap/parameters/zpool 2>/dev/null || true - echo ${toString cfg.maxPoolPercent} > /sys/module/zswap/parameters/max_pool_percent 2>/dev/null || true - echo ${toString cfg.acceptThresholdPercent} > /sys/module/zswap/parameters/accept_threshold_percent 2>/dev/null || true - echo Y > /sys/module/zswap/parameters/same_filled_pages_enabled 2>/dev/null || true - echo Y > /sys/module/zswap/parameters/writeback 2>/dev/null || true - - # Enable zswap - echo Y > /sys/module/zswap/parameters/enabled 2>/dev/null || true - - echo "Zswap enabled with compressor=${cfg.compressor}, zpool=${cfg.zpool}, max_pool_percent=${toString cfg.maxPoolPercent}%, accept_threshold_percent=${toString cfg.acceptThresholdPercent}%" - else - echo "Warning: zswap sysfs interface not available. Check kernel configuration." - fi - ''; + # 3. Runtime configuration using boot.kernel.sysfs + # This ensures zswap parameters are properly set and maintained during system rebuilds + boot.kernel.sysfs.module.zswap.parameters = { + enabled = true; + compressor = cfg.compressor; + zpool = cfg.zpool; + max_pool_percent = cfg.maxPoolPercent; + accept_threshold_percent = cfg.acceptThresholdPercent; + shrinker_enabled = true; }; assertions = [