Brief overview of Wine architecture...
Wine Overview
Foreword
Wine is often used as a recursive acronym, standing for “Wine Is Not an
Emulator”. Sometimes it is also known to be used for “Windows Emulator”.
In a way, both meanings are correct, only seen from different
perspectives. The first meaning says that Wine is not a virtual machine,
it does not emulate a CPU, and you are not supposed to install Windows
nor any Windows device drivers on top of it; rather, Wine is an
implementation of the Windows API, and can be used as a library to port
Windows applications to Unix. The second meaning, obviously, is that to
Windows binaries (.exe
files), Wine does look like Windows, and
emulates its behaviour and quirks rather closely.
Note: The “Emulator” perspective should not be thought of as if Wine is a typical inefficient emulation layer that means Wine can't be anything but slow - the faithfulness to the badly designed Windows API may of course impose a minor overhead in some cases, but this is both balanced out by the higher efficiency of the Unix platforms Wine runs on, and that other possible abstraction libraries (like Motif, GTK+, CORBA, etc) has a runtime overhead typically comparable to Wine's.
Executables
Wine main task is to run Windows executables under non Windows operating systems. It supports different types of executables:
- DOS executable. Those are even older programs, using the DOS format
(either
.com
or.exe
(the later being also called MZ)). - Windows NE executable, also called 16-bit. They were the native processes run by Windows 2.x and 3.x. NE stands for New Executable (!).
- Windows PE executable. These are programs were introduced in Windows 95 (and became the native formats for all later Windows versions), even if 16-bit applications were still supported. PE stands for Portable Executable, in a sense where the format of the executable (as a file) is independent of the CPU (even if the content of the file - the code - is CPU dependent).
- Winelib executable. These are applications, written using the Windows API, but compiled as a Unix executable. Wine provides the tools to create such executables.
Let's quickly review the main differences for the supported executables:
DOS (.COM or .EXE ) |
Win16 (NE) | Win32 (PE) | Win64 (PE) | Winelib | |
---|---|---|---|---|---|
Multitasking | Only one application at a time (except for TSR) | Cooperative | Preemptive | Preemptive | Preemptive |
Address space | One MB of memory, where each application is loaded and unloaded. | All 16-bit applications share a single address space, protected mode. | Each application has it's own address space. Requires MMU support from CPU. | Each application has it's own address space. Requires MMU support from CPU. | Each application has it's own address space. Requires MMU support from CPU. |
Windows API | No Windows API but the DOS API (like Int 21h traps). | Will call the 16-bit Windows API. | Will call the 32-bit Windows API. | Will call the 64-bit Windows API. | Will call the 32/64-bit Windows API, and possibly also the Unix APIs. |
Code (CPU level) | Only available on x86 in real mode. Code and data are in segmented forms, with 16-bit offsets. Processor is in real mode. | Only available on IA‑32 architectures, code and data are in segmented forms, with 16-bit offsets (hence the 16-bit name). Processor is in protected mode. | Available (with NT) on several CPUs, including IA‑32. On this CPU, uses a flat memory model with 32-bit offsets (hence the 32-bit name). | Only available on AMD64 and AArch64 architectures. | Flat model, with 32/64-bit addresses. |
Multi-threading | Not available. | Not available. | Available. | Available. | Available, but must use the Win32/64 APIs for threading and synchronization, not the Unix ones. |
Wine deals with this issue by launching a separate Wine process (which is in fact a Unix process) for each Win32 process, but not for Win16 tasks. Win16 tasks are run as different intersynchronized Unix-threads in the same dedicated Wine process; this Wine process is commonly known as a WOW process (Windows on Windows), referring to a similar mechanism used by Windows NT.
Synchronization between the Win16 tasks running in the WOW process is normally done through the Win16 mutex - whenever one of them is running, it holds the Win16 mutex, keeping the others from running. When the task wishes to let the other tasks run, the thread releases the Win16 mutex, and one of the waiting threads will then acquire it and let its task run.
winevdm is the Wine process dedicated to running the Win16
processes. Note that several instances of this process could exist, as
Windows has support for different VDM (Virtual Dos Machines) in order to
have Win16 processes running in different address spaces. Wine also uses
the same architecture to run DOS programs (in this case, the DOS
emulation is provided by KRNL386.EXE
).
Standard Windows Architectures
Windows 9x architecture
The windows architecture (Win 9x way) looks like this:
+---------------------+ \
| Windows EXE | } application
+---------------------+ /
+---------+ +---------+ \
| Windows | | Windows | \ application & system DLLs
| DLL | | DLL | /
+---------+ +---------+ /
+---------+ +---------+ \
| GDI32 | | USER32 | \
| DLL | | DLL | \
+---------+ +---------+ } core system DLLs
+---------------------+ /
| Kernel32 DLL | /
+---------------------+ /
+---------------------+ \
| Win9x kernel | } kernel space
+---------------------+ /
+---------------------+ \
| Windows low-level | \ drivers (kernel space)
| drivers | /
+---------------------+ /
Windows NT architecture
The windows architecture (Windows NT way) looks like the following
drawing. Note the new DLL (NTDLL
) which allows implementing different
subsystems (as win32); kernel32
in NT architecture implements the
Win32 subsystem on top of NTDLL
.
+---------------------+ \
| Windows EXE | } application
+---------------------+ /
+---------+ +---------+ \
| Windows | | Windows | \ application & system DLLs
| DLL | | DLL | /
+---------+ +---------+ /
+---------+ +---------+ +-----------+ \
| GDI32 | | USER32 | | | \
| DLL | | DLL | | | \
+---------+ +---------+ | | \ core system DLLs
+---------------------+ | | / (on the left side)
| Kernel32 DLL | | Subsystem | /
| (Win32 subsystem) | |Posix, OS/2| /
+---------------------+ +-----------+ /
+---------------------------------------+
| NTDLL.DLL |
+---------------------------------------+
+---------------------------------------+ \
| NT kernel | } NT kernel (kernel space)
+---------------------------------------+ /
+---------------------------------------+ \
| Windows low-level drivers | } drivers (kernel space)
+---------------------------------------+ /
Note also (not depicted in schema above) that the 16-bit applications are supported in a specific subsystem. Some basic differences between the Win9x and the NT architectures include:
- Several subsystems (Win32, Posix...) can be run on NT, while not on Win 9x.
- Win 9x roots its architecture in 16-bit systems, while NT is truly a 32-bit system.
- The drivers model and interfaces in Win 9x and NT are different (even if Microsoft tried to bridge the gap with some support of WDM drivers in Win 98 and above).
Wine architecture
Global picture
Wine implementation is closer to the Windows NT architecture, even if several subsystems are not implemented yet (remind also that 16-bit support is implemented in a 32-bit Windows EXE, not as a subsystem). Here's the overall picture:
+---------------------+ \
| Windows EXE | } application
+---------------------+ /
+---------+ +---------+ \
| Windows | | Windows | \ application & system DLLs
| DLL | | DLL | /
+---------+ +---------+ /
+---------+ +---------+ +-----------+ +--------+ \
| GDI32 | | USER32 | | | | | \
| DLL | | DLL | | | | Wine | \
+---------+ +---------+ | | | Server | \ core system DLLs
+---------------------+ | | | | / (on the left side)
| Kernel32 DLL | | Subsystem | | NT-like| /
| (Win32 subsystem) | |Posix, OS/2| | Kernel | /
+---------------------+ +-----------+ | | /
| |
+---------------------------------------+ | |
| NTDLL | | |
+---------------------------------------+ +--------+
+---------------------------------------+ \
| Wine executable | } unix executable
+---------------------------------------+ /
+---------------------------------------------------+ \
| Wine drivers | } Wine specific DLLs
+---------------------------------------------------+ /
+------------+ +------------+ +--------------+ \
| libc | | libX11 | | other libs | } unix shared libraries
+------------+ +------------+ +--------------+ / (user space)
+---------------------------------------------------+ \
| Unix kernel (Linux,*BSD,Solaris,OS/X) | } (Unix) kernel space
+---------------------------------------------------+ /
+---------------------------------------------------+ \
| Unix device drivers | } Unix drivers (kernel space)
+---------------------------------------------------+ /
Wine must at least completely replace the “Big Three” DLLs
(KERNEL
/KERNEL32
, GDI
/GDI32
, and USER
/USER32
), which all
other DLLs are layered on top of. But since Wine is (for various
reasons) leaning towards the NT way of implementing things, the NTDLL
is another core DLL to be implemented in Wine, and many KERNEL32
and
ADVAPI32
features will be implemented through the NTDLL
.
As of today, no real subsystem (apart the Win32 one) has been implemented in Wine.
The Wine server provides the backbone for the implementation of the core DLLs. It mainly implements inter-process synchronization and object sharing. It can be seen, from a functional point of view, as a NT kernel (even if the APIs and protocols used between Wine DLLs and the Wine server are Wine-specific).
Wine uses the Unix drivers to access the various hardware pieces on the box. However, in some cases, Wine will provide a driver (in Windows sense) to a physical hardware device. This driver will be a proxy to the Unix driver (this is the case, for example, for the graphical part with X11 or Mac drivers, audio with OSS or ALSA drivers...).
All DLLs provided by Wine try to stick as much as possible to the
exported APIs from the Windows platforms. There are rare cases where
this is not the case, and have been properly documented (Wine DLLs
export some Wine specific APIs). Usually, those are prefixed with
__wine
.
Let's now review in greater details all of those components.
The Wine server
The Wine server is among the most confusing concepts in Wine. What is
its function in Wine? Well, to be brief, it provides Inter-Process
Communication (IPC), synchronization, and process/thread management.
When the Wine server launches, it creates a Unix socket for the current
host based on (see below) your home directory's .wine
subdirectory (or
wherever the WINEPREFIX
environment variable points to) - all Wine
processes launched later connects to the Wine server using this socket.
If a Wine server was not already running, the first Wine process will
start up the Wine server in auto-terminate mode (i.e. the Wine server
will then terminate itself once the last Wine process has terminated).
The master socket mentioned above is created within the /tmp
directory
with a name that reflects the configuration directory. This means that
there can actually be several separate copies of the Wine server
running; one per combination of user and configuration directory. Note
that you should not have several users using the same configuration
directory at the same time; they will have different copies of the Wine
server running and this could well lead to problems with the registry
information that they are sharing.
Every thread in each Wine process has its own request buffer, which is
shared with the Wine server. When a thread needs to synchronize or
communicate with any other thread or process, it fills out its request
buffer, then writes a command code through the socket. The Wine server
handles the command as appropriate, while the client thread waits for a
reply. In some cases, like with the various WaitFor???
synchronization
primitives, the server handles it by marking the client thread as
waiting and does not send it a reply before the wait condition has been
satisfied.
The Wine server itself is a single and separate Unix process and does
not have its own threading - instead, it is built on top of a large
poll()
loop that alerts the Wine server whenever anything happens,
such as a client having sent a command, or a wait condition having been
satisfied. There is thus no danger of race conditions inside the Wine
server itself - it is often called upon to do operations that look
completely atomic to its clients.
Because the Wine server needs to manage processes, threads, shared handles, synchronization, and any related issues, all the clients' Win32 objects are also managed by the Wine server, and the clients must send requests to the Wine server whenever they need to know any Win32 object handle's associated Unix file descriptor (in which case the Wine server duplicates the file descriptor, transmits it back to the client, and leaves it to the client to close the duplicate when the client has finished with it).
Wine builtin DLLs: about Relays, Thunks, and DLL descriptors
This section mainly applies to builtin DLLs (DLLs provided by Wine). See section Wine/Windows DLLs for the details on native vs. builtin DLL handling.
Loading a Windows binary into memory isn't that hard by itself, the hard
part is all those various DLLs and entry points it imports and expects
to be there and function as expected; this is, obviously, what the
entire Wine implementation is all about. Wine contains a range of DLL
implementations. You can find the DLLs implementation in the dlls/
directory.
Each DLL (at least, the 32-bit version, see below) is implemented in a
Unix shared library. The file name of this shared library is the module
name of the DLL with a .dll.so
suffix (or .drv.so
or any other
relevant extension depending on the DLL type). This shared library
contains the code itself for the DLL, as well as some more information,
as the DLL resources and a Wine specific DLL descriptor.
The DLL descriptor, when the DLL is instantiated, is used to create an in-memory PE header, which will provide access to various information about the DLL, including but not limited to its entry point, its resources, its sections, its debug information...
The DLL descriptor and entry point table is generated by the
winebuild tool, taking DLL specification files with the extension
.spec
as input. Resources (after compilation by wrc) or message
tables (after compilation by wmc) are also added to the
descriptor by winebuild.
When an application module wants to import a DLL, Wine will look:
- through its list of registered DLLs (in fact, both the already loaded DLLs, and the already loaded shared libraries which have registered a DLL descriptor). Since, the DLL descriptor is automatically registered when the shared library is loaded - remember, registration call is put inside a shared library constructor.
- If it's not registered, Wine will look for it on disk, building the
shared library name from the DLL module name. Directory searched for
are specified by the
WINEDLLPATH
environment variable. - Failing that, it will look for a real Windows
.DLL
file to use, and look through its imports, etc) and use the loading of native DLLs.
After the DLL has been identified (assuming it's still a native one),
it's mapped into memory using a dlopen()
call. Note that Wine doesn't
use the shared library mechanisms for resolving and/or importing
functions between two shared libraries (for two DLLs). The shared
library is only used for providing a way to load a piece of code on
demand. This piece of code, thanks to the DLL descriptor, will provide
the same type of information a native DLL would. Wine can then use the
same code for native and builtin DLL to handle imports/exports.
Wine also relies on the dynamic loading features of the Unix shared libraries to relocate the DLLs if needed (the same DLL can be loaded at different addresses in two different processes, and even in two consecutive runs of the same executable if the order in which the DLLs are loaded differs).
The DLL descriptor is registered in the Wine realm using some tricks.
The winebuild tool, while creating the code for DLL descriptor,
also creates a constructor, that will be called when the shared library
is loaded into memory. This constructor will actually register the
descriptor to the Wine DLL loader. Hence, before the dlopen
call
returns, the DLL descriptor will be known and registered. This also
helps to deal with the cases where there are still dependencies (at the
ELF shared lib level, not at the embedded DLL level) between different
shared libraries: the embedded DLLs will be properly registered, and
even loaded (from a Windows point of view).
Since Wine is 32-bit code itself, and if the compiler supports Windows
calling convention, stdcall
(gcc does), Wine can resolve
imports into Win32 code by substituting the addresses of the Wine
handlers directly without any thunking layer in between. This eliminates
the overhead most people associate with “emulation”, and is what the
applications expect anyway.
However, if the user specified WINEDEBUG=+relay
, a thunk layer is
inserted between the application imports and the Wine handlers (actually
the export table of the DLL is modified, and a thunk is inserted in the
table); this layer is known as “relay” because all it does is print out
the arguments/return values (by using the argument lists in the DLL
descriptor's entry point table), then pass the call on, but it's
invaluable for debugging misbehaving calls into Wine code. A similar
mechanism also exists between Windows DLLs - Wine can optionally insert
thunk layers between them, by using WINEDEBUG=+snoop
, but since no DLL
descriptor information exists for non-Wine DLLs, this is less reliable
and may lead to crashes.
For Win16 code, there is no way around thunking - Wine needs to relay between 16-bit and 32-bit code. These thunks switch between the app's 16-bit stack and Wine's 32-bit stack, copies and converts arguments as appropriate (integer sizes differ, pointers are segmented in 16-bit but are 32-bit linear values in 32-bit), and handles the Win16 mutex. Some finer control can be obtained on the conversion, see winebuild reference manual for the details. Suffices to say that the kind of intricate stack content juggling this results in, is not exactly suitable study material for beginners.
Wine/Windows DLLs
This document mainly deals with the status of current DLL support by Wine. winecfg currently supports settings to change the load order of DLLs. The load order depends on several issues, which results in different settings for various DLLs.
Pros of Native DLLs
Native DLLs of course guarantee 100% compatibility for routines they
implement. For example, using the native USER
DLL would maintain a
virtually perfect and Windows 95-like look for window borders, dialog
controls, and so on. Using the built-in Wine version of this library, on
the other hand, would produce a display that does not precisely mimic
that of Windows 95. Such subtle differences can be engendered in other
important DLLs, such as the common controls library COMMCTRL
or the
common dialogs library COMMDLG
, when built-in Wine DLLs outrank other
types in load order.
More significant, less aesthetically-oriented problems can result if the
built-in Wine version of the SHELL
DLL is loaded before the native
version of this library. SHELL
contains routines such as those used by
installer utilities to create desktop shortcuts. Some installers might
fail when using Wine's built-in SHELL
.
Cons of Native DLLs
Not every application performs better under native DLLs. If a library
tries to access features of the rest of the system that are not fully
implemented in Wine, the native DLL might work much worse than the
corresponding built-in one, if at all. For example, the native Windows
GDI
library must be paired with a Windows display driver, which of
course is not present under Unix and Wine.
Finally, occasionally built-in Wine DLLs implement more features than
the corresponding native Windows DLLs. Probably the most important
example of such behavior is the integration of Wine with X provided by
Wine's built-in USER
DLL. Should the native Windows USER
library
take load-order precedence, such features as the ability to use the
clipboard or drag-and-drop between Wine windows and X windows will be
lost.
Deciding Between Native and Built-In DLLs
Clearly, there is no one rule-of-thumb regarding which load-order to use. So, you must become familiar with what specific DLLs do and which other DLLs or features a given library interacts with, and use this information to make a case-by-case decision.
Load Order for DLLs
The default load order follows this algorithm: for all DLLs which have a fully-functional Wine implementation, or where the native DLL is known not to work, the built-in library will be loaded first. In all other cases, the native DLL takes load-order precedence.
See the Wine User's Guide for information on how to change the settings.
Memory management
Every Win32 process in Wine has its own dedicated native process on the host system, and therefore its own address space. This section explores the layout of the Windows address space and how it is emulated.
Firstly, a quick recap of how virtual memory works. Physical memory in RAM chips is split into frames, and the memory that each process sees is split into pages. Each process has its own 4 gigabytes of address space (4 GB being the maximum space addressable with a 32-bit pointer). Pages can be mapped or unmapped: attempts to access an unmapped page cause an EXCEPTION_ACCESS_VIOLATION which has the easily recognizable code of 0xC0000005. Any page can be mapped to any frame, therefore you can have multiple addresses which actually “contain” the same memory. Pages can also be mapped to things like files or swap space, in which case accessing that page will cause a disk access to read the contents into a free frame.
Initial layout (in Windows)
When a Win32 process starts, it does not have a clear address space to use as it pleases. Many pages are already mapped by the operating system. In particular, the EXE file itself and any DLLs it needs are mapped into memory, and space has been reserved for the stack and a couple of heaps (zones used to allocate memory to the app from). Some of these things need to be at a fixed address, and others can be placed anywhere.
The EXE file itself is usually mapped at address 0x400000
and up:
indeed, most EXEs have their relocation records stripped which means
they must be loaded at their base address and cannot be loaded at any
other address.
DLLs are internally much the same as EXE files but they have relocation
records, which means that they can be mapped at any address in the
address space. Remember we are not dealing with physical memory here,
but rather virtual memory which is different for each process. Therefore
OLEAUT32.DLL
may be loaded at one address in one process, and a
totally different one in another. Ensuring all the functions loaded into
memory can find each other is the job of the Windows dynamic linker,
which is a part of NTDLL
.
So, we have the EXE and its DLLs mapped into memory. Two other very
important regions also exist: the stack and the process heap. The
process heap is simply the equivalent of the libc malloc
arena on
UNIX: it's a region of memory managed by the OS which
malloc
/HeapAlloc
partitions and hands out to the application.
Windows applications can create several heaps but the process heap
always exists.
Windows 9x also implements another kind of heap: the shared heap. The shared heap is unusual in that anything allocated from it will be visible in every other process.
Comparison
So far we've assumed the entire 4 gigs of address space is available for
the application. In fact that's not so: only the lower 2 gigs are
available, the upper 2 gigs are on Windows NT used by the operating
system and hold the kernel (from 0x80000000
). Why is the kernel mapped
into every address space? Mostly for performance: while it's possible to
give the kernel its own address space too - this is what Ingo Molnar's
4G/4G VM split patch does for Linux - it requires that every system call
into the kernel switches address space. As that is a fairly expensive
operation (requires flushing the translation lookaside buffers etc) and
syscalls are made frequently it's best avoided by keeping the kernel
mapped at a constant position in every processes address space.
Basically, the comparison of memory mappings looks as follows:
Address | Windows 9x | Windows NT | Linux |
---|---|---|---|
00000000-7fffffff | User | User | User |
80000000-bfffffff | Shared | User | User |
c0000000-ffffffff | Kernel | Kernel | Kernel |
On Windows 9x, in fact only the upper gigabyte (0xC0000000
and up) is
used by the kernel, the region from 2 to 3 gigs is a shared area used
for loading system DLLs and for file mappings. The bottom 2 gigs on both
NT and 9x are available for the programs memory allocation and stack.
Wine drivers
Wine will not allow running native Windows drivers under Unix. This
comes mainly because (look at the generic architecture schemas) Wine
doesn't implement the kernel features of Windows (kernel here really
means the kernel, not the KERNEL32
DLL), but rather sets up a proxy
layer on top of the Unix kernel to provide the NTDLL
and KERNEL32
features. This means that Wine doesn't provide the inner infrastructure
to run native drivers, either from the Win9x family or from the NT
family.
In other words, Wine will only be able to provide access to a specific device, if and only if, 1/ this device is supported in Unix (there is Unix-driver to talk to it), 2/ Wine has implemented the proxy code to make the glue between the API of a Windows driver, and the Unix interface of the Unix driver.
Wine, however, tries to implement in the various DLLs needing to access devices to do it through the standard Windows APIs for device drivers in user space. This is for example the case for the multimedia drivers, where Wine loads Wine builtin DLLs to talk to the OSS interface, or the ALSA interface. Those DLLs implement the same interface as any user space audio driver in Windows.