So far in this chapter, you've seen the structures that are part of a process and the API functions with which you (and the operating system) can manipulate processes. You've also found out how you can use tools to view how processes interact with your system. But how did those processes come into being, and how do they exit once they've fulfilled their purpose? In the following sections, you'll discover how a Win32 process comes to life.
A Win32 process is created when an application calls one of the process creation functions, such as CreateProcess, CreateProcessAsUser, or CreateProcessWithLogonW. Creating a Win32 process consists of several stages carried out in three parts of the operating system: the Win32 client-side library Kernel32.dll, the Windows 2000 executive, and the Win32 subsystem process (Csrss). Because of the multiple environment subsystem architecture of Windows 2000, creating a Windows 2000 executive process object (which other subsystems can use) is separated from the work involved in creating a Win32 process. So, although the following description of the flow of the Win32 CreateProcess function is complicated, keep in mind that part of the work is specific to the semantics added by the Win32 subsystem as opposed to the core work needed to create a Windows 2000 executive process object.
The following list summarizes the main stages of creating a process with the Win32 CreateProcess function. The operations performed in each stage are described in detail in the subsequent sections.
- Open the image file (.exe) to be executed inside the process.
- Create the Windows 2000 executive process object.
- Create the initial thread (stack, context, and Windows 2000 executive thread object).
- Notify the Win32 subsystem of the new process so that it can set up for the new process and thread.
- Start execution of the initial thread (unless the CREATE_SUSPENDED flag was specified).
- In the context of the new process and thread, complete the initialization of the address space (such as load required DLLs) and begin execution of the program.
Figure 6-5 shows an overview of the stages Windows 2000 follows to create a process.
Figure 6-5 The main stages of process creation
Before describing these stages in more detail, we should mention a few notes that pertain to all the stages.
- In CreateProcess, the priority class for the new process is specified as independent bits in the CreationFlags parameter. Thus, you can specify more than one priority class for a single CreateProcess call. Windows 2000 resolves the question of which priority class to assign to the process by choosing the lowest-priority class set.
- If no priority class is specified for the new process, the priority class defaults to Normal unless the priority class of the process that created it is Idle or Below Normal, in which case the priority class of the new process will have the same priority as the creating class.
- If a Real-time priority class is specified for the new process and the process's caller doesn't have the Increase Scheduling Priority privilege, the High priority class is used instead. In other words, CreateProcess doesn't fail just because the caller has insufficient privileges to create the process in the Real-time priority class; the new process just won't have as high a priority as Real-time.
- All windows are associated with desktops, the graphical representation of a workspace. If no desktop is specified in CreateProcess, the process is associated with the caller's current desktop.
Enough background. The steps of CreateProcess are described in detail in the following sections.
Many steps of CreateProcess are related to the setup of the process virtual address space and hence refer to many memory management terms and structures, which are defined in Chapter 7.
Stage 1: Opening the Image to Be Executed
As illustrated in Figure 6-6, the first stage in CreateProcess is to find the appropriate Win32 image that will run the executable file specified by the caller and to create a section object to later map it into the address space of the new process. If no image name is specified, the first token of the command line (defined to be the first part of the command-line string ending with a space or tab that is a valid file specification) is used as the image filename.
Figure 6-6 Choosing a Win32 image to activate
If the executable file specified is a Win32 .exe, it is used directly. If it's not a Win32 .exe (for example, if it's an MS-DOS, a Win16, a POSIX, or an OS/2 application), CreateProcess goes through a series of steps to find a Win32 support image to run it. This process is necessary because non-Win32 applications aren't run directly—Windows 2000 instead uses one of a few special support images that in turn are responsible for actually running the non-Win32 program. For example, if you attempt to run a POSIX application, CreateProcess identifies it as such and changes the image to be run on the Win32 executable file Posix.exe. If you attempt to run an MS-DOS or a Win16 executable, the image to be run becomes the Win32 executable Ntvdm.exe. In short, you can't directly create a process that is not a Win32 process. If Windows 2000 can't find a way to resolve the activated image as a Win32 process (as shown in Table 6-6), CreateProcess fails.
Table 6-6 Decision Tree for Stage 1 of CreateProcess
|If the image is a/an ||This image will run ||And this will happen |
|POSIX executable file ||Posix.exe ||CreateProcess restarts Stage 1|
|OS/2 1.x image ||Os2.exe ||CreateProcess restarts Stage 1|
|MS-DOS application with an .exe, a .com, or a .pif extension ||Ntvdm.exe ||CreateProcess restarts Stage 1|
|Win16 application ||Ntvdm.exe ||CreateProcess restarts Stage 1|
|Command procedure MS-DOS application with a .bat or a .cmd extension ||Cmd.exe ||CreateProcess restarts Stage 1|
Specifically, the decision tree that CreateProcess goes through to run an image is as follows:
- If the image is an OS/2 1.x application, the image to be run changes to Os2.exe and CreateProcess restarts at Stage 1.
- If the image is an MS-DOS application with an .exe, a .com, or a .pif extension, a message is sent to the Win32 subsystem to check whether an MS-DOS support process (Ntvdm.exe, specified in the registry value HKLM\SYSTEM\CurrentControlSet\Control\WOW\cmdline) has already been created for this session. If a support process has been created, it is used to run the MS-DOS application (the Win32 subsystem sends the message to the VDM [Virtual DOS Machine] process to run the new image) and CreateProcess returns. If a support process hasn't been created, the image to be run changes to Ntvdm.exe and CreateProcess restarts at Stage 1.
- If the file to run has a .bat or a .cmd extension, the image to be run becomes Cmd.exe, the Windows 2000 command prompt, and CreateProcess restarts at Stage 1. (The name of the batch file is passed as the first parameter to Cmd.exe.)
- If the image is a Win16 (Windows 3.1) executable, CreateProcess must decide whether a new VDM process must be created to run it or whether it should use the default systemwide shared VDM process (which might not yet have been created). The CreateProcess flags CREATE_SEPARATE_WOW_VDM and CREATE_SHARED_WOW_VDM control this decision. If these flags aren't specified, the registry value HKLM\SYSTEM\CurrentControlSet\Control\WOW\DefaultSeparateVDM dictates the default behavior. If the application is to be run in a separate VDM, the image to be run changes to the value of HKLM\SYSTEM\CurrentControlSet\Control\WOW\wowcmdline and CreateProcess restarts at Stage 1. Otherwise, the Win32 subsystem sends a message to see whether the systemwide VDM process exists and can be used. (If the VDM process is running on a different desktop or isn't running under the same security as the caller, it can't be used and a new VDM process must be created.) If a systemwide VDM process can be used, the Win32 subsystem sends a message to it to run the new image and CreateProcess returns. If the VDM process hasn't yet been created (or if it exists but can't be used), the image to be run changes to the VDM support image and CreateProcess restarts at Stage 1.
At this point, CreateProcess has successfully opened a valid Windows 2000 executable file and created a section object for it. The object isn't mapped into memory yet, but it is open. Just because a section object has been successfully created doesn't mean that the file is a valid Win32 image, however; it could be a DLL or a POSIX executable. If the file is a POSIX executable, the image to be run changes to Posix.exe and CreateProcess restarts from the beginning of Stage 1. If the file is a DLL, CreateProcess fails.
Now that CreateProcess has found a valid Win32 executable, it looks in the registry under HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options to see whether a subkey with the filename and extension of the executable image (but without the directory and path information—for example, Image.exe) exists there. If it does, CreateProcess looks for a value named Debugger for that key. If the value isn't null, the image to be run becomes the string in that value and CreateProcess restarts at Stage 1.
You can take advantage of this CreateProcess behavior and debug the startup code of Windows 2000 service processes before they start rather than attach the debugger after starting the service, which doesn't allow you to debug the startup code. If you're feeling mischievous, you can also exploit this behavior to confuse people by causing another file to be run rather than the one they specified.
Stage 2: Creating the Windows 2000 Executive Process Object
At this point, CreateProcess has opened a valid Win32 executable file and created a section object to map it into the new process address space. Next it creates a Windows 2000 executive process object to run the image by calling the internal system function NtCreateProcess. Creating the executive process object (which is done by the creating thread) involves the following substages:
- Setting up the EPROCESS block
- Creating the initial process address space
- Creating the kernel process block
- Concluding the setup of the process address space
- Setting up the PEB
- Completing the setup of the executive process object
The only time there won't be a parent process is during system initialization. After that point, a parent process is always required in order to provide a security context for the new process.
Stage 2A: Setting Up the EPROCESS Block
This substage involves five steps:
- Allocate and initialize the Windows 2000 EPROCESS block.
- Set the new process's quota block to the address of its parent process's quota block, and increment the reference count for the parent's quota block.
- Store the parent process's process ID and session ID (if applicable) in the InheritedFromUniqueProcessId field in the new process object.
- Set the new process's exit status to STATUS_PENDING.
- Create the process's primary access token (a duplicate of its parent's primary token). New processes inherit the security profile of their parent. If the CreateProcessAsUser function is being used to specify a different access token for the new process, the token is then changed appropriately.
Stage 2B: Creating the Initial Process Address Space
The initial process address space consists of three pages:
- Page directory
- Hyperspace page
- Working set list
To create these three pages, the following steps are taken:
- Page table entries are created in the appropriate page tables to map the three initial pages.
- To account for these new pages, the value 3 is deducted from the kernel variable MmTotalCommittedPages and added to MmProcessCommit.
- The systemwide default process minimum working set size (PsMinimumWorkingSet) is deducted from MmResident-AvailablePages.
- The page table pages for the nonpaged portion of system space and the system cache are mapped into the process.
- The process minimum and maximum working set size are set to the values of PsMinimumWorkingSet and PsMaximumWorkingSet, respectively.
Stage 2C: Creating the Kernel Process Block
The next stage of CreateProcess is the initialization of the KPROCESS block, which contains a pointer to a list of kernel threads. (The kernel has no knowledge of handles, so it bypasses the object table.) The kernel process block also points to the process's page table directory (used to keep track of the process's virtual address space), the total time the process's threads have executed, the process's default base-scheduling priority (which starts as Normal, or 8, unless the parent process was set to Idle or Below Normal, in which case the setting is inherited), the default processor affinity for the threads in the process, and the initial value of the process default quantum, which is taken from the value of PspForegroundQuantum, the first entry in the systemwide quantum array.
The default initial quantum differs between Windows 2000 Professional and Windows 2000 Server. For more information on thread quantums, turn to their discussion in the section "Thread Scheduling."
Stage 2D: Concluding the Setup of the Process Address Space
Setting up the address space for a new process is somewhat complicated, so let's look at what's involved one step at a time. To get the most out of this section, you should have some familiarity with the internals of the Windows 2000 memory manager, which are described in Chapter 7.
- The virtual memory manager sets the value of the process's last trim time to the current time. The working set manager (which runs in the context of the balance set manager system thread) uses this value to determine when to initiate working set trimming.
- The page frame number (PFN) database for the page directory as well as the page directory entry, which maps hyperspace, are initialized.
- The memory manager initializes the process's working set list—page faults can now be taken.
- The major and minor version numbers are copied from the executable file to the EPROCESS block.
- The section (created when the image file was opened) is now mapped into the new process's address space, and the process section base address is set to the base address of the image.
- Ntdll.dll is mapped into the process.
- The systemwide national language support (NLS) tables are mapped into the process's address space.
POSIX processes clone the address space of their parents, so they don't have to go through these steps to create a new address space. In the case of POSIX applications, the new process's section base address is set to that of its parent process and the parent's PEB is cloned for the new process.
- If the parent process was contained in a job, the new process is added to the job. (Jobs are described at the end of this chapter.)
- CreateProcess inserts the new process block at the end of the Windows 2000 list of active processes (PsActiveProcessHead).
Stage 2E: Setting Up the PEB
CreateProcess allocates a page for the PEB and initializes a number of fields, which are described in Table 6-7.
Table 6-7 Initial Values of the Fields of the PEB
|Field ||Initial Value |
|ImageBaseAddress ||Base address of section|
|NumberOfProcessors ||KeNumberProcessors kernel variable|
|NtGlobalFlag ||NtGlobalFlag kernel variable|
|CriticalSectionTimeout ||MmCriticalSectionTimeout kernel variable|
|HeapSegmentReserve ||MmHeapSegmentReserve kernel variable|
|HeapSegmentCommit ||MmHeapSegmentCommit kernel variable|
|HeapDeCommitTotalFreeThreshold ||MmHeapDeCommitTotalFreeThreshold kernel variable|
|HeapDeCommitFreeBlockThreshold ||MmHeapDeCommitFreeBlockThreshold kernel variable|
|MaximumNumberOfHeaps ||(Size of a page - size of a PEB) / 4|
|ProcessHeaps ||First byte after PEB|
|OSMajorVersion ||NtMajorVersion kernel variable|
|OSMinorVersion ||NtMinorVersion kernel variable|
|OSBuildNumber ||NtBuildNumber kernel variable & 0x3FFF|
If the image file specifies explicit Win32 version values, this information replaces the initial values shown in Table 6-7. The mapping from image version information fields to PEB fields is described in Table 6-8.
Table 6-8 Win32 Replacements for Initial PEB Values
|Field Name ||Value Taken from Image Header |
|OSMajorVersion ||OptionalHeader.Win32VersionValue & 0xFF|
|OSMinorVersion ||(OptionalHeader.Win32VersionValue >> 8) & 0xFF|
|OSBuildNumber ||(OptionalHeader.Win32VersionValue >> 16) & 0x3FFF|
|OSPlatformId ||(OptionalHeader.Win32VersionValue >> 30) ^ 0x2|
Stage 2F: Completing the Setup of the Executive Process Object
Before the handle to the new process can be returned, a few final setup steps must be completed:
- The process handle table is initialized; if the inherit handles flag is set for the parent process, any inheritable handles are copied from the parent's object handle table into the new process. (For more information about object handle tables, see Chapter 3.)
- If you're running Windows 2000 Professional and the image header specifies IMAGE_FILE_AGGRESIVE_WS_TRIM, the PS_WS_TRIM_FROM_EXE_HEADER flag is set in the process block. This causes the working set manager to aggressively steal pages from the process. If you're running Windows 2000 Professional on a small-memory x86 system, the PS_WS_TRIM_BACKGROUND_ONLY_APP flag is set in the process block, which limits the aggressive trimming to processes that aren't associated with the foreground window. These working set flags are not set for processes created on systems running Windows 2000 Server.
- If the image header characteristics IMAGE_FILE_UP_SYSTEM_ONLY flag is set (indicating that the image can run only on a uniprocessor system), a single CPU is chosen for all the threads in this new process to run on. This choosing process is done by simply cycling through the available processors—each time this type of image is run, the next processor is used. In this way, these types of images are spread out across the processors evenly.
- If the image specifies an explicit processor affinity mask (for example, a field in the configuration header), this value is copied to the PEB and later set as the default process affinity mask.
- If the parent process had an Event Log section in its PEB, the Event Log is copied to the new process and a handle is duplicated to the section for the new process.
- If systemwide auditing of processes is enabled (which is accomplished through the Group Policy snap-in), the process's creation is written to the Audit Log.
- The process's creation time is set, the handle to the new process is returned to the caller (CreateProcess in Kernel32.dll), and execution returns to the original caller of CreateProcess.
Stage 3: Creating the Initial Thread and Its Stack and Context
At this point, the Windows 2000 executive process object is completely set up. It still has no thread, however, so it can't do anything yet. Before the thread can be created, it needs a stack and a context in which to run, so these are set up now. The stack size for the initial thread is taken from the image—there's no way to specify another size.
Now the initial thread can be created, which is done by calling NtCreateThread. (For a detailed description of how a thread is created, see the section "Flow of CreateThread.") The thread parameter (which can't be specified in CreateProcess but can be specified in CreateThread) is the address of the PEB. This parameter will be used by the initialization code that runs in the context of this new thread (as described in Stage 6). However, the thread won't do anything yet—it is created in a suspended state and isn't resumed until the process is completely initialized (as described in Stage 5).
Stage 4: Notifying the Win32 Subsystem About the New Process
After all the necessary executive process and thread objects have been created, Kernel32.dll sends a message to the Win32 subsystem so that it can set up for the new process and thread. The message includes the following information:
- Process and thread handles
- Entries in the creation flags
- ID of the process's creator
- Flag indicating whether the process belongs to a Win32 application (so that Csrss can determine whether or not to show the startup cursor)
The Win32 subsystem performs the following steps when it receives this message:
- CreateProcess duplicates a handle for the process and thread. In this step, the usage count of the process and the thread is incremented from 1 (set at creation time) to 2.
- If a process priority class isn't specified, CreateProcess sets it according to the algorithm described below.
- The Csrss process block is allocated.
- The new process's exception port is set to be the general function port for the Win32 subsystem so that the Win32 subsystem will receive a message when an exception occurs in the process. (For further information on exception handling, see Chapter 3.)
- If the process is being debugged (that is, if it is attached to a debugger process), the process debug port is set to the Win32 subsystem's general function port. This setting ensures that Windows 2000 will send debug events that occur in the new process (such as thread creation and deletion, exceptions, and so on) as messages to the Win32 subsystem so that it can then dispatch the events to the process that is acting as the new process's debugger.
- The Csrss thread block is allocated and initialized.
- CreateProcess inserts the thread in the list of threads for the process.
- The count of processes in this session is incremented.
- The process shutdown level is set to x280 (the default process shutdown level—see SetProcessShutdownParameters in the MSDN Library documentation for more information).
- The new process block is inserted into the list of Win32 subsystem-wide processes.
- The per-process data structure used by the kernel-mode part of the Win32 subsystem (W32PROCESS structure) is allocated and initialized.
- The application start cursor is displayed. This cursor is the familiar arrow with an hourglass attached—the way that Windows 2000 says to the user, "I'm starting something, but you can use the cursor in the meantime." If the process doesn't make a GUI call after 2 seconds, the cursor reverts to the standard pointer. If the process does make a GUI call in the allotted time, CreateProcess waits 5 seconds for the application to show a window. After that time, CreateProcess will reset the cursor again.
Stage 5: Starting Execution of the Initial Thread
At this point, the process environment has been determined, resources for its threads to use have been allocated, the process has a thread, and the Win32 subsystem knows about the new process. Unless the caller specified the CREATE_SUSPENDED flag, the initial thread is now resumed so that it can start running and perform the remainder of the process initialization work that occurs in the context of the new process (Stage 6).
Stage 6: Performing Process Initialization in the Context of the New Process
KiInitializeContextThread, which is called by KeInitializeThread, builds the initial context of the thread and the thread's kernel stack. The new thread begins life running the kernel-mode thread startup routine KiThreadStartup. (For a more detailed description of the thread startup steps leading to this, see the section "Flow of CreateThread.") The KiThreadStartup routine performs the following steps:
- Lowers the IRQL level from DPC/dispatch level to APC (asynchronous procedure call) level.
- Enables working set expansion.
- Queues a user-mode APC to the new thread to execute the user-mode thread startup routine LdrInitializeThunk inside Ntdll.dll.
- Lowers the IRQL level to 0, causing the APC to fire and LdrInitializeThunk to be called. The LdrInitializeThunk routine initializes the loader, heap manager, NLS tables, thread-local storage (TLS) array, and critical section structures. It then loads any required DLLs and calls the DLL entry points with the DLL_PROCESS_ATTACH function code.
- If the process being created is a debuggee, all threads in the process are suspended. (Threads might have been created during step 3.) A create process message is then sent to the process's debug port (the Win32 subsystem function port because this is a Win32 process) so that the subsystem can deliver the process startup debug event (CREATE_PROCESS_DEBUG_INFO) to the appropriate debugger process. KiThreadStartup then waits for the Win32 subsystem to get the reply from the debugger (via the ContinueDebugEvent function). When the Win32 subsystem replies, all the threads are resumed.
- Finally, the image begins execution in user mode. This is done by creating a trap frame that specifies the previous mode as user and the address to return to as the main entry point of the image. Thus, when the trap that caused the thread to start execution in kernel mode is dismissed, the program begins running in user mode at the right place.