diff --git a/Configuration/UTMQemuConfiguration+Arguments.swift b/Configuration/UTMQemuConfiguration+Arguments.swift index 6e5db53e60..cd3f45d7a8 100644 --- a/Configuration/UTMQemuConfiguration+Arguments.swift +++ b/Configuration/UTMQemuConfiguration+Arguments.swift @@ -551,6 +551,11 @@ import Virtualization // for getting network interfaces } } + /// Keeps guest RAM resident so the host does not compress or swap it. + private var isMemoryLockUsed: Bool { + system.architecture == .x86_64 && isHypervisorUsed && qemu.hasMemoryLock + } + private var isTSOUsed: Bool { system.architecture.hasTSOSupport && qemu.hasTSO } @@ -694,6 +699,10 @@ import Virtualization // for getting network interfaces f("-m") system.memorySize f() + if isMemoryLockUsed { + f("-overcommit") + f("mem-lock=on") + } } var hasCustomBios: Bool { diff --git a/Configuration/UTMQemuConfigurationQEMU.swift b/Configuration/UTMQemuConfigurationQEMU.swift index 411f83a1de..4f3717ea16 100644 --- a/Configuration/UTMQemuConfigurationQEMU.swift +++ b/Configuration/UTMQemuConfigurationQEMU.swift @@ -57,6 +57,8 @@ struct UTMQemuConfigurationQEMU: Codable { /// Add an interrupt remapping IOMMU for x86_64 q35 guests using the hypervisor on Intel. var hasInterruptRemapping: Bool = true + /// Lock guest RAM in host memory for x86_64 guests using the hypervisor on Intel. + var hasMemoryLock: Bool = true /// QEMU machine property that overrides the default property defined by UTM. var machinePropertyOverride: String? @@ -96,6 +98,7 @@ struct UTMQemuConfigurationQEMU: Codable { case hasRTCLocalTime = "RTCLocalTime" case hasPS2Controller = "PS2Controller" case hasInterruptRemapping = "InterruptRemapping" + case hasMemoryLock = "MemoryLock" case machinePropertyOverride = "MachinePropertyOverride" case additionalArguments = "AdditionalArguments" } @@ -115,6 +118,7 @@ struct UTMQemuConfigurationQEMU: Codable { hasRTCLocalTime = try values.decode(Bool.self, forKey: .hasRTCLocalTime) hasPS2Controller = try values.decode(Bool.self, forKey: .hasPS2Controller) hasInterruptRemapping = try values.decodeIfPresent(Bool.self, forKey: .hasInterruptRemapping) ?? true + hasMemoryLock = try values.decodeIfPresent(Bool.self, forKey: .hasMemoryLock) ?? true machinePropertyOverride = try values.decodeIfPresent(String.self, forKey: .machinePropertyOverride) additionalArguments = try values.decode([QEMUArgument].self, forKey: .additionalArguments) if let dataURL = decoder.userInfo[.dataURL] as? URL { @@ -136,6 +140,7 @@ struct UTMQemuConfigurationQEMU: Codable { try container.encode(hasRTCLocalTime, forKey: .hasRTCLocalTime) try container.encode(hasPS2Controller, forKey: .hasPS2Controller) try container.encode(hasInterruptRemapping, forKey: .hasInterruptRemapping) + try container.encode(hasMemoryLock, forKey: .hasMemoryLock) try container.encodeIfPresent(machinePropertyOverride, forKey: .machinePropertyOverride) try container.encode(additionalArguments, forKey: .additionalArguments) } diff --git a/Platform/Shared/VMConfigQEMUView.swift b/Platform/Shared/VMConfigQEMUView.swift index c951232285..b3e83d67a4 100644 --- a/Platform/Shared/VMConfigQEMUView.swift +++ b/Platform/Shared/VMConfigQEMUView.swift @@ -67,6 +67,8 @@ struct VMConfigQEMUView: View { if config.system.architecture == .x86_64 && config.qemu.hasHypervisor { Toggle("Interrupt remapping", isOn: $config.qemu.hasInterruptRemapping) .help("Lets the guest use faster interrupt handling, which reduces stutter and audio dropouts in Windows. Requires the Q35 machine.") + Toggle("Lock guest memory", isOn: $config.qemu.hasMemoryLock) + .help("Keeps the guest's memory resident so macOS does not compress or swap it. The Mac needs enough free memory for the whole guest.") } #endif Toggle("Use local time for base clock", isOn: $config.qemu.hasRTCLocalTime)