Re: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled

From: Alexander Lougovski

Date: Tue Jul 28 2026 - 13:52:39 EST


On Mon, Jul 27, 2026 at 01:23:03PM -0600, Tycho Andersen wrote:
> Can you share the code for the mmap + disk io + DB content generator?
>
> We're looking at this and would like to reproduce.

Hi Tycho,

Sure, here's everything below.

The setup is a Windows Server 2022 guest on QEMU/KVM with Driver
Verifier enabled (/standard /all, which includes Special Pool). All
three workloads run concurrently inside the guest.

1) Memory-mapped file workload (PowerShell, runs as a scheduled task)

Two concurrent background jobs, each in an infinite loop: pick a
random system DLL, read it into a byte array, create a memory-mapped
file from it, touch pages through the view accessor, then dispose.
Every 50 iterations, GC.Collect() forces the CLR to finalize all the
disposed mappings at once, which triggers a burst of PTE teardowns in
the Windows memory manager - in theory that should put some pressure on the TLB.

--- 8< --- workload-1582.ps1 ---
$ErrorActionPreference = 'Stop'
$logFile = "C:\drivers\workload-1582-errors.log"

$files = @(
"$env:SystemRoot\System32\ntdll.dll",
"$env:SystemRoot\System32\kernel32.dll",
"$env:SystemRoot\System32\user32.dll",
"$env:SystemRoot\System32\advapi32.dll",
"$env:SystemRoot\System32\ole32.dll",
"$env:SystemRoot\System32\shell32.dll",
"$env:SystemRoot\System32\comctl32.dll",
"$env:SystemRoot\System32\msvcrt.dll"
)

$block = {
param($files, $logFile)
$i = 0
while ($true) {
$f = $files[(Get-Random -Maximum $files.Count)]
try {
$bytes = [System.IO.File]::ReadAllBytes($f)
$ms = [System.IO.MemoryStream]::new($bytes)
$mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateFromFile(
$f, [System.IO.FileMode]::Open, $null,
0, [System.IO.MemoryMappedFiles.MemoryMappedFileAccess]::Read)
$view = $mmf.CreateViewAccessor(0, 0,
[System.IO.MemoryMappedFiles.MemoryMappedFileAccess]::Read)
for ($p = 0; $p -lt [Math]::Min($view.Capacity, 65536); $p += 4096) {
$null = $view.ReadByte($p)
}
$view.Dispose()
$mmf.Dispose()
$ms.Dispose()
} catch {
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') iter=$i file=$f error=$($_.Exception.Message)" | Out-File -Append $logFile
}
$i++
if ($i % 50 -eq 0) {
try {
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
} catch {
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') iter=$i GC error=$($_.Exception.Message)" | Out-File -Append $logFile
}
}
}
}

"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Workload1582 starting" | Out-File -Append $logFile

1..2 | ForEach-Object {
Start-Job -ScriptBlock $block -ArgumentList (,$files), $logFile
}

while ($true) {
$jobs = Get-Job
$failed = $jobs | Where-Object { $_.State -eq 'Failed' }
if ($failed) {
foreach ($j in $failed) {
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Job $($j.Id) FAILED: $($j.ChildJobs[0].JobStateInfo.Reason)" | Out-File -Append $logFile
Remove-Job $j -Force
Start-Job -ScriptBlock $block -ArgumentList (,$files), $logFile
}
}
Start-Sleep -Seconds 60
}
--- 8< ---

Register as a startup task:

schtasks /create /tn Workload1582 /sc onstart /ru SYSTEM /f ^
/tr "powershell.exe -ExecutionPolicy Bypass -File C:\workload-1582.ps1"

2) Disk I/O (DiskSpd, runs as a scheduled task)

Single-threaded, 32 outstanding I/Os, 64K block size, 50/50
read/write, 128MB file, loop forever.

--- 8< --- diskspd-loop.cmd ---
:loop
C:\drivers\diskspd.exe -d604800 -t1 -o32 -b64K -w50 -Sh -c128M C:\diskspd-io.dat
goto loop
--- 8< ---

DiskSpd binary: https://github.com/microsoft/diskspd/releases

Register:

schtasks /create /tn DiskSpdIO /sc onstart /ru SYSTEM /f ^
/tr "C:\drivers\diskspd-loop.cmd"

3) SQL stress (host-side script, drives queries into the guest)

SQL Server Express 2022 runs inside the guest. The database
(StressDB, SIMPLE recovery model) has a single table with ~1.6M rows:

RandomData (
ID INT IDENTITY PRIMARY KEY,
Col1 UNIQUEIDENTIFIER,
Col2 NVARCHAR(100),
Col3 NVARCHAR(100),
Col4 NVARCHAR(100),
Col5 DATETIME2,
Col6 FLOAT,
Col7 BIGINT,
Col8 VARBINARY(200)
)

In our setup the database is baked into the golden VM image, so we
don't have a standalone creation script. To reproduce, seed random
data and double via INSERT...SELECT until you reach ~1.6M rows. The
exact count doesn't matter much — it just needs to be larger than the
SQL Express buffer pool (1.4 GB) so that queries cause constant cache
misses and disk I/O.

The host runs 3 sqlcmd streams per VM, 75% writes / 25% reads:

--- 8< --- sql-stress-75w.sh ---
#!/usr/bin/env bash
SQLCMD=/opt/mssql-tools18/bin/sqlcmd

query_stream() {
local TARGET=$1
while true; do
case $((RANDOM % 4)) in
0) Q="SET NOCOUNT ON; DECLARE @s INT = ABS(CHECKSUM(NEWID())) % 2800000 + 1; UPDATE StressDB.dbo.RandomData SET Col2 = REPLICATE(N'X', 90 + ABS(CHECKSUM(NEWID())) % 10), Col3 = REPLICATE(N'Y', 90 + ABS(CHECKSUM(NEWID())) % 10), Col5 = SYSDATETIME(), Col7 = ABS(CHECKSUM(NEWID())) WHERE ID BETWEEN @s AND @s + 50000" ;;
1) Q="SET NOCOUNT ON; DECLARE @s INT = ABS(CHECKSUM(NEWID())) % 2800000 + 1; UPDATE StressDB.dbo.RandomData SET Col4 = REPLICATE(N'Z', 90 + ABS(CHECKSUM(NEWID())) % 10), Col6 = RAND(CHECKSUM(NEWID())) * 1000, Col8 = CAST(REPLICATE(0x42, 100 + ABS(CHECKSUM(NEWID())) % 100) AS VARBINARY(200)) WHERE ID BETWEEN @s AND @s + 50000" ;;
2) Q="SET NOCOUNT ON; DECLARE @s INT = ABS(CHECKSUM(NEWID())) % 2800000 + 1; UPDATE StressDB.dbo.RandomData SET Col2 = REPLICATE(N'W', 90 + ABS(CHECKSUM(NEWID())) % 10), Col5 = SYSDATETIME(), Col7 = ABS(CHECKSUM(NEWID())), Col8 = CAST(REPLICATE(0x43, 150) AS VARBINARY(200)) WHERE ID BETWEEN @s AND @s + 50000; CHECKPOINT" ;;
3) Q="SET NOCOUNT ON; DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS; CHECKPOINT; SELECT TOP 5000 ID, Col2, Col3, Col4, Col8 FROM StressDB.dbo.RandomData WITH (NOLOCK) WHERE Col7 > ABS(CHECKSUM(NEWID())) % 2000000000 ORDER BY Col5 DESC" ;;
esac
$SQLCMD -S "$TARGET" -U sa -P 'TestPass123!' -C -Q "$Q" -t 120 > /dev/null 2>&1
done
}

echo "Starting 3 streams per VM (42 VMs = 126 streams)..."
echo "75% writes (cases 0,1,2): UPDATE 50K rows with wide columns + CHECKPOINT on case 2"
echo "25% reads (case 3): DROPCLEANBUFFERS + CHECKPOINT + table scan"
for N in $(seq 1 42); do
IP="192.168.100.$((10 + N))"
query_stream "$IP,1433" &
query_stream "$IP,1433" &
query_stream "$IP,1433" &
done
wait
--- 8< ---

On top of all that, Windows Defender runs a full scan of all drives
every 30 minutes via a scheduled task.

Al