This page documents the client-side section path built on top of local-file handles.
Wine-NSPA no longer has to mint a wineserver section object for every eligible
CreateFileMapping on a local-file handle. The client process can keep a
bounded section table of its own, duplicate the unix fd at section-creation
time, and service the common same-process view lifecycle without a server
round-trip.
The feature sits on top of the local-file bypass rather than replacing it. A
local file handle still owns pathname resolution, open semantics, and the
cross-process sharing aggregate. The local section path reuses that file handle
state to build an unnamed file-backed section, then publishes mapping bits back
into the same aggregate so later file operations still see correct
STATUS_USER_MAPPED_FILE and sharing behavior.
The current boundary is intentionally narrow:
DuplicateHandle is supported by promoting to a server sectionThat is enough to retire a large volume of mapping RPC traffic from the common case while leaving the hard cross-process edge cases on the authoritative server path.
| Surface | Current behavior |
|---|---|
NtCreateSection |
Eligible file-backed mappings on local-file handles can mint a client-side section handle instead of calling wineserver. |
NtMapViewOfSection / NtMapViewOfSectionEx |
Same-process maps on local section handles install the view directly in the client process. |
NtUnmapViewOfSectionEx |
Local section views unmap without a server hop. |
NtQuerySection |
SectionBasicInformation is answered locally; image-only queries still fall back or return the honest non-image status where appropriate. |
NtClose |
Local section close tears down local views and releases the duplicated unix fd; if a same-process duplicate already promoted the section, the cached server handle is also released. |
NtDuplicateObject |
Same-process duplicate of a local section promotes once to a server section, caches that server handle, and then lets the duplicate proceed. |
| File/section coherence | Mapping bits are published into the local-file aggregate so later share checks and FileEndOfFileInformation handling still see active mappings. |
This path is the default for eligible unnamed file-backed sections.
The local section path is deliberately smaller than the full NT section surface. A section stays local only when all of the following are true:
SEC_IMAGESEC_LARGE_PAGESAnything outside that envelope falls back to the normal server path. That keeps the feature easy to reason about and avoids pretending that cross-process namespace or image-loader semantics can be reconstructed client-side.
Local section handles live in their own client-private range:
[0x7FFF8000, 0x7FFFC000)[0x7FFFC000, 0x80000000)That split matters because later APIs can distinguish a local section from a local file with a cheap range check before they decide which local table to consult.
Each local section entry keeps:
(dev, inode) identityNtUnmapViewOfSectionEx and NtClose can
tear them down coherentlyThe table is process-local and protected by its own PI mutex. Cross-process coordination is not done through the section table itself; only the mapping effects are published into the shared local-file aggregate.
The local section lifecycle is a same-process fast path from creation to final
close. The main change versus the older path is that the unix fd is duplicated
at section creation time, so the mapping can survive a later CloseHandle() on
the original file handle.
Two points are worth calling out:
CloseHandle(file) no longer invalidates the later map path for an eligible
local section, because the section owns its own duplicated unix fd.The local section path is same-process first. That is deliberate.
When NtDuplicateObject() sees a local section handle and both the source and
destination process are the current process, it promotes the section once to a
server section, caches that promoted handle on the local entry, and then lets
the duplicate proceed through the normal server machinery.
When the duplicate crosses a process boundary, the client-side section handle stops being a valid abstraction. The server does not have access to the remote process’s private section table, so the current behavior is to fall through cleanly rather than invent cross-process state. That produces the right failure instead of a confusing partially-working handle.
This is one of the key differences between a local optimization and a fake object namespace. The local section handle is a process-local implementation detail until the call explicitly crosses into server-owned handle semantics.
The local section path stays correct by feeding its mapping state back into the same shared aggregate that local-file already uses for open and sharing arbitration.
When a local section is created:
(dev, inode) from the local-file entryFILE_MAPPING_WRITE, FILE_MAPPING_IMAGE, or
FILE_MAPPING_ACCESS bits into the shared aggregate, matching the mapping
shapeThat shared publication matters for two reasons:
FileEndOfFileInformation can still reject a shrink with
STATUS_USER_MAPPED_FILE when a mapped section is activeThis is the architectural seam between the file path and the section path: the file table owns the cross-process aggregate, and the section table reuses it instead of inventing a second source of truth.
The local section path was validated on the real workload that exposed the old cost: repeated file mapping and view traffic during app startup and UI initialization.
Key observed results from the landed implementation:
CreateFile -> CreateFileMapping -> CloseHandle(file) -> MapViewOfFilenspa_create_mapping_from_unix_fd count dropped from 2,664 to ~800
(-70%)get_mapping_info and unmap_view dropped out of the top sampled symbols1,991 ms to 1,077 ms on the
cleanest run of that comparisonSTATUS_INVALID_HANDLE instead of an
inconsistent handle stateThe companion local-file follow-ons matter here too. Because mapping bits are
published into the shared aggregate, later file-side changes like local
FileEndOfFileInformation handling can preserve the same mapped-file boundary
without reintroducing a mandatory section RPC.
| Path | Role |
|---|---|
dlls/ntdll/unix/nspa/local_file.c |
local section table, handle range, create path, mapping-bit publication, same-process duplicate support |
dlls/ntdll/unix/sync.c |
NtCreateSection / NtCreateSectionEx entrypoint hooks |
dlls/ntdll/unix/virtual.c |
NtMapViewOfSection, NtMapViewOfSectionEx, local unmap, view tagging, and NtQuerySection support |
dlls/ntdll/unix/server.c |
NtDuplicateObject promotion boundary and NtClose teardown |
dlls/ntdll/unix/unix_private.h |
shared declarations for local-file and local-section helpers |
The design intentionally stays inside existing Wine layers:
sync.c decides whether section creation can stay localvirtual.c owns view installation and teardownserver.c remains the place where an explicit server-handle boundary is
crossedThat keeps the patch understandable and reduces the number of call sites that need to know about the feature.