Skip to content

nvidia: use nv_vmalloc when pt_size >= PMD_SIZE to avoid direct compaction under lock - #1383

Open
hyuuko1 wants to merge 1 commit into
NVIDIA:mainfrom
hyuuko1:fix-page-table-compaction
Open

hyuuko1 wants to merge 1 commit into
NVIDIA:mainfrom
hyuuko1:fix-page-table-compaction

Conversation

@hyuuko1

@hyuuko1 hyuuko1 commented Sep 22, 2026

Copy link
Copy Markdown

Avoid blocking other processes due to direct compaction while holding g_RmApiLock

Overview

When allocating the page table in nvos_create_alloc() and pt_size >= PMD_SIZE (2MB on x86_64), use nv_vmalloc() instead of kvzalloc(). This prevents the driver from triggering 2MB huge page allocation and time-consuming direct compaction while holding the g_RmApiLock write lock, fixing the issue where other GPU-dependent processes are blocked for a long time.

Problem

In Linux 5.18 and later kernels, commit 9becb6889130 ("kvmalloc: use vmalloc_huge for vmalloc allocations") modified kvmalloc() behavior: when the requested allocation size is greater than or equal to PMD_SIZE, it automatically passes the VM_ALLOW_HUGE_VMAP flag.

When registering 512MB or more memory, the page table array size pt_size reaches 2MB (i.e. PMD_SIZE). In nvos_create_alloc(), this array was previously allocated with kvzalloc(pt_size, NV_GFP_KERNEL).

This allocation happens while holding the g_RmApiLock write lock. Because the size reaches PMD_SIZE, kvzalloc() attempts to allocate 2MB huge pages. When physical memory is heavily fragmented (for example, when too much pinned memory prevents page migration, making compaction difficult), the kernel enters the slow path and runs synchronous direct compaction, performing page migration and cross-core TLB flushes.

The main problem is that during memory compaction, this thread keeps holding the g_RmApiLock global write lock. As a result, all other processes in the system that need GPU access are severely blocked in uninterruptible sleep (D state), halting workload progress. Under severe fragmentation, the blocking duration can even exceed the system-configured hung_task_timeout_secs threshold (configured as 28 seconds on our machines), causing the kernel to dump hung task call traces.

Call Traces

  1. Process blocked waiting for the g_RmApiLock write lock:
INFO: task nfd-worker:575494 blocked for more than 28 seconds.
      Tainted: G           OE K    6.1.180-2 #2
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:nfd-worker      state:D stack:0     pid:575494 ppid:575399 flags:0x00000000
Call Trace:
 <TASK>
 __schedule+0x2b6/0x900
 schedule+0x68/0xf0
 rwsem_down_write_slowpath+0x2e4/0x6b0
 down_write+0x65/0x70
 os_acquire_rwlock_write+0x31/0x40 [nvidia]
 portSyncRwLockAcquireWrite+0xc/0x30 [nvidia]
 rmapiLockAcquire+0x294/0x360 [nvidia]
 serverTopLock_Prologue+0x58/0xe0 [nvidia]
 serverAllocResource+0x1bb/0x520 [nvidia]
 ? os_acquire_spinlock+0x12/0x30 [nvidia]
 ? mntput_no_expire+0x53/0x270
 rmapiAllocWithSecInfo+0x1b4/0x380 [nvidia]
 rmapiAllocWithSecInfoTls+0x61/0x90 [nvidia]
 ? mntput_no_expire+0x53/0x270
 ? vfs_statx+0x8a/0x150
 Nv04AllocWithAccessSecInfo+0x6f/0x80 [nvidia]
 ? ns_capable_common+0x32/0x50
 RmIoctl+0xc21/0xf40 [nvidia]
 ? os_get_monotonic_time_ns+0x2c/0x90 [nvidia]
 ? portSyncSpinlockAcquire+0x14/0x20 [nvidia]
 ? os_release_spinlock+0x1a/0x20 [nvidia]
 rm_ioctl+0x62/0x3f0 [nvidia]
 ? do_syscall_64+0x61/0x80
 nvidia_ioctl.isra.0+0x824/0x9a0 [nvidia]
 nvidia_unlocked_ioctl+0x22/0x30 [nvidia]
 __x64_sys_ioctl+0x92/0xd0
 x64_sys_call+0x1bca/0x1fd0
 do_syscall_64+0x54/0x80
 ? vfs_statx+0x8a/0x150
 ? putname+0x55/0x70
 ? vfs_fstatat+0x64/0x80
 ? __do_sys_newstat+0x35/0x70
 ? syscall_exit_to_user_mode+0x1e/0x40
 ? do_syscall_64+0x61/0x80
 ? __audit_syscall_entry+0xce/0x140
 ? syscall_exit_to_user_mode+0x1e/0x40
 ? do_syscall_64+0x61/0x80
 ? syscall_exit_to_user_mode+0x1e/0x40
 ? do_syscall_64+0x61/0x80
 ? syscall_exit_to_user_mode+0x1e/0x40
 ? do_syscall_64+0x61/0x80
 ? exc_page_fault+0x73/0x160
 entry_SYSCALL_64_after_hwframe+0x6e/0xd8
RIP: 0033:0x7f8e49e4e4a7
RSP: 002b:00007f8de4ff8438 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000030 RCX: 00007f8e49e4e4a7
RDX: 00007f8de4ff8640 RSI: 00000000c030462b RDI: 000000000000001a
RBP: 00007f8de4ff8490 R08: 00007f8de4ff8640 R09: 00007f8de4ff8668
R10: 00007f8de4ff7860 R11: 0000000000000246 R12: 00007f8de4ff8640
R13: 000000000000001a R14: 00000000c030462b R15: 00007f8de4ff8450
 </TASK>
  1. Process holding the write lock and performing direct compaction in the kernel:
NMI backtrace for cpu 147
CPU: 147 PID: 1899175 Comm: aemeath-rover-99 Kdump: loaded Tainted: G           OE K    6.1.180-2 #2
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 03/18/2026
RIP: 0010:smp_call_function_many_cond+0xec/0x2c0
Code: 08 48 63 d0 e8 05 e7 3f 00 39 05 3f 11 1a 02 76 26 48 63 d0 49 8b 0c 24 48 03 0c d5 60 cb f8 90 8b 51 08 83 e2 01 74 0a f3 90 <8b> 51 08 83 e2 01 75 f6 83 c0 01 eb bf 48 83 c4 40 5b 41 5c 41 5d
RSP: 0018:ffffcd0c3fa5e748 EFLAGS: 00000202
RAX: 0000000000000078 RBX: 0000000000000001 RCX: ffff8b09ed438de0
RDX: 0000000000000001 RSI: 0000000000000180 RDI: ffff8a55cd6766c0
RBP: ffffcd0c3fa5e7b0 R08: 0000000000000078 R09: 0000000000000000
R10: 0000000000000005 R11: 0000000000000006 R12: ffff8b09edaf1780
R13: ffffffff8f888470 R14: ffff8b09edaf00c0 R15: ffff8b09edaf1780
FS:  00007fd240d7e000(0000) GS:ffff8b09edac0000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000e8722c97010 CR3: 000000bc55e5a001 CR4: 0000000000770ee0
PKRU: 55555554
Call Trace:
 <TASK>
 ? leave_mm+0x50/0x50
 on_each_cpu_cond_mask+0x29/0x50
 native_flush_tlb_multi+0x5b/0x120
 ? __mod_memcg_lruvec_state+0x45/0x90
 flush_tlb_mm_range+0x11b/0x160
 ptep_clear_flush+0x5e/0x70
 try_to_migrate_one+0x232/0xc60
 ? remove_migration_pte+0x237/0x4e0
 rmap_walk_file+0xdc/0x310
 rmap_walk+0x2f/0x40
 try_to_migrate+0x8e/0xd0
 ? try_to_unmap_one+0xd80/0xd80
 ? anon_vma_ctor+0x50/0x50
 ? folio_get_anon_vma+0x90/0x90
 migrate_pages_batch+0x6b8/0xae0
 ? __bpf_trace_mm_compaction_kcompactd_sleep+0x20/0x20
 ? fast_isolate_freepages+0x7a0/0x7a0
 migrate_pages+0x4a6/0x550
 ? __bpf_trace_mm_compaction_kcompactd_sleep+0x20/0x20
 ? fast_isolate_freepages+0x7a0/0x7a0
 compact_zone+0x740/0xd90
 ? asm_sysvec_call_function_single+0x1b/0x20
 compact_zone_order+0xbb/0x100
 try_to_compact_pages+0xf4/0x2f0
 __alloc_pages_direct_compact+0x91/0x1f0
 __alloc_pages_slowpath.constprop.0+0x51c/0xef0
 ? asm_sysvec_call_function_single+0x1b/0x20
 __alloc_pages+0x324/0x370
 alloc_pages+0x90/0x160
 __vmalloc_area_node+0x1aa/0x5c0
 __vmalloc_node_range+0xc2/0x210
 kvmalloc_node+0xbc/0xf0
 ? nvos_create_alloc+0x7d/0x150 [nvidia]
 nvos_create_alloc+0x7d/0x150 [nvidia]
 nv_register_user_pages+0x55/0x140 [nvidia]
 osCreateMemFromOsDescriptor+0x525/0xe30 [nvidia]
 ? sysvec_call_function_single+0x4e/0x90
 osdescConstruct_IMPL+0x17c/0x867 [nvidia]
 ? gpuGetByRef+0xec/0x130 [nvidia]
 ? __kmem_cache_alloc_node+0x403/0x490
 ? CliGetGpuFromContext+0x11/0x20 [nvidia]
 ? memConstruct_IMPL+0x88/0x10f [nvidia]
 ? __kmalloc+0x4f/0x130
 ? __nvoc_ctor_Memory+0x4e/0x70 [nvidia]
 __nvoc_ctor_OsDescMemory+0x4e/0x70 [nvidia]
 __nvoc_objCreate_OsDescMemory+0x10f/0x1c0 [nvidia]
 __nvoc_objCreateDynamic+0x46/0x70 [nvidia]
 ? _portMemAllocPagedUntracked+0x2c/0x40 [nvidia]
 resservResourceFactory+0xaf/0x230 [nvidia]
 _clientAllocResourceHelper+0x2aa/0x690 [nvidia]
 ? serverResLock_Prologue+0x283/0x2f0 [nvidia]
 ? rmGpuGroupLockAcquire+0x117/0x1a0 [nvidia]
 serverAllocResourceUnderLock+0x44b/0x9f0 [nvidia]
 ? portSyncSpinlockAcquire+0x14/0x20 [nvidia]
 ? portThreadGetCurrentThreadId+0x19/0x20 [nvidia]
 ? portThreadGetCurrentThreadId+0x19/0x20 [nvidia]
 ? rmclientValidateLocks_IMPL+0x1d/0x90 [nvidia]
 ? _serverLockClientWithLockInfo.constprop.0+0x106/0x230 [nvidia]
 serverAllocResource+0x2aa/0x520 [nvidia]
 ? os_acquire_spinlock+0x12/0x30 [nvidia]
 ? balance_dirty_pages_ratelimited+0x10/0x20
 rmapiAllocWithSecInfo+0x1b4/0x380 [nvidia]
 rmapiAllocWithSecInfoTls+0x61/0x90 [nvidia]
 ? ttwu_queue_wakelist+0xf9/0x100
 _rmAllocForDeprecatedApi+0x21/0x30 [nvidia]
 _nvos32FunctionAllocOsDesc+0x63/0x70 [nvidia]
 RmDeprecatedVidHeapControl+0x6b/0x80 [nvidia]
 Nv04VidHeapControlWithSecInfo+0x31/0x40 [nvidia]
 ? rmapiControlWithSecInfoTls+0xc0/0xc0 [nvidia]
 ? _rmAllocForDeprecatedApi+0x30/0x30 [nvidia]
 ? _rmControlForDeprecatedApi+0x20/0x20 [nvidia]
 ? _rmFreeForDeprecatedApi+0x20/0x20 [nvidia]
 ? RmCopyUserForDeprecatedApi+0xe0/0xe0 [nvidia]
 ? _rmMapMemoryForDeprecatedApi+0x30/0x30 [nvidia]
 ? _rmAllocMemForDeprecatedApi+0x10/0x10 [nvidia]
 ? up_read+0x51/0x60
 RmCreateOsDescriptor+0x9d/0x160 [nvidia]
 ? os_alloc_mem+0xc9/0xe0 [nvidia]
 RmIoctl+0xd87/0xf40 [nvidia]
 ? os_get_monotonic_time_ns+0x2c/0x90 [nvidia]
 ? os_acquire_spinlock+0x12/0x30 [nvidia]
 ? os_release_spinlock+0x1a/0x20 [nvidia]
 rm_ioctl+0x62/0x3f0 [nvidia]
 nvidia_ioctl.isra.0+0x824/0x9a0 [nvidia]
 nvidia_unlocked_ioctl+0x22/0x30 [nvidia]
 __x64_sys_ioctl+0x92/0xd0
 x64_sys_call+0x1bca/0x1fd0
 do_syscall_64+0x54/0x80
 ? syscall_exit_to_user_mode+0x1e/0x40
 ? do_syscall_64+0x61/0x80
 ? syscall_exit_to_user_mode+0x1e/0x40
 ? do_syscall_64+0x61/0x80
 entry_SYSCALL_64_after_hwframe+0x6e/0xd8
RIP: 0033:0x7fd75b9dcded
Code: 04 25 28 00 00 00 48 89 45 c8 31 c0 48 8d 45 10 c7 45 b0 10 00 00 00 48 89 45 b8 48 8d 45 d0 48 89 45 c0 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1a 48 8b 45 c8 64 48 2b 04 25 28 00 00 00
RSP: 002b:00007fd240d4c1e0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007fd240d4c2e0 RCX: 00007fd75b9dcded
RDX: 00007fd240d4c2e0 RSI: 00000000c0384627 RDI: 000000000000012b
RBP: 00007fd240d4c230 R08: 00007fd240d4c2e0 R09: 00007fd240d4c308
R10: 00007fd032e79a30 R11: 0000000000000246 R12: 00000000c0384627
R13: 000000000000012b R14: 00007fd240d4c308 R15: 00007fd240d4c250
 </TASK>

Solution

The page_table array only needs to be contiguous in kernel virtual address space for driver access. It does not require physically contiguous memory, nor does it require 2MB huge page mappings.

We adjust the threshold in nvos_create_alloc() and nvos_free_alloc() from INT_MAX to PMD_SIZE:

  • When pt_size >= PMD_SIZE, use nv_vmalloc(). It calls __vmalloc() without VM_ALLOW_HUGE_VMAP, allocating only standard 4KB pages and avoiding synchronous direct compaction.
  • When pt_size < PMD_SIZE, continue using kvzalloc(), preserving fast kmalloc() allocations for small sizes.
  • Since PMD_SIZE is much smaller than INT_MAX, the original protection logic for oversized page tables (> INT_MAX) is fully preserved.

Reproduction Environment

GPU: 8x NVIDIA RTX PRO 6000 Blackwell Server Edition
Kernel: Linux 6.1.180-2
Driver version: 580.95.05
CUDA Version: 13.0

Verification

After applying this change, bpftrace showed no huge page allocations on this call path, and the issue could no longer be reproduced.

sudo bpftrace -e 'rawtracepoint:mm_page_alloc /arg1 >= 9/ { @[comm, arg0 != 0 ? "SUCCESS" : "FAILED", kstack] = count(); } interval:s:10 { time("%H:%M:%S\n"); print(@); }'

…ction under lock

In Linux 5.18 and later kernels, commit 9becb6889130 ("kvmalloc: use
vmalloc_huge for vmalloc allocations") changed the behavior of kvmalloc():
when the requested allocation size is greater than or equal to PMD_SIZE
(2MB on x86_64), it automatically adds the VM_ALLOW_HUGE_VMAP flag.

When registering 512MB or more memory, the page table array size pt_size
reaches 2MB (PMD_SIZE). In nvos_create_alloc(), this array was previously
allocated using kvzalloc(pt_size, NV_GFP_KERNEL).

Because the size reaches PMD_SIZE, kvzalloc() attempts to allocate 2MB
huge pages. When physical memory is heavily fragmented, the kernel enters
synchronous direct compaction to assemble 2MB contiguous physical memory.
This process involves page migration and cross-core TLB flushes, which can
take a very long time.

Crucially, this allocation is performed while holding the global write lock
g_RmApiLock. Holding this write lock exclusively during memory compaction
causes other processes in the system that need GPU access to be blocked in
uninterruptible sleep (D state). In severe cases, the blocking time exceeds
the hung_task_timeout_secs threshold.

In practice, the page_table array only needs to be virtually contiguous for
driver addressing and indexing. It does not require physically contiguous
memory, nor does it require 2MB huge page mappings.

To fix this: when pt_size >= PMD_SIZE, use nv_vmalloc() instead of kvzalloc().
nv_vmalloc() calls __vmalloc() without huge page flags, allocating only
standard 4KB pages and completely avoiding the direct compaction overhead of
2MB huge pages.

Since PMD_SIZE is much smaller than INT_MAX, this change also covers and
preserves the original protection logic for oversized page tables (> INT_MAX).

Signed-off-by: Jialin Wang <wjl.linux@gmail.com>
@CLAassistant

CLAassistant commented Sep 22, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants