Processes

A process is usually defined as an instance of a running program and consists of two components:

  • A kernel object that the operating system uses to manage the process. The kernel object is also where the system keeps statistical information about the process.
  • An address space that contains all the executable or dynamic-link library (DLL) module’s code and data. It also contains dynamic memory allocations such as thread stacks and heap allocations.

Processes are inert. For a process to accomplish anything it must have a thread that runs in its context; this thread is responsible for executing the code contained in the process’ address space. In fact, a single process might contain several threads, all of them executing code “simultaneously” in the process’ address space. To do this, each thread has its own set of CPU registers and its own stack. Each process has at least one thread that executes code in the process’ address space.

If there were no threads executing code in the process’ address space, there would be no reason for the process to continue to exist, and the system would automatically destroy the process and its address space.

When you use Microsoft Visual Studio to create an application project, the integrated environment sets up various linker switches so that the linker embeds the proper type of subsystem in the resulting executable. This linker switch is /SUBSYSTEM:CONSOLE for CUI applications and /SUBSYSTEM:WINDOWS for GUI applications. When the user runs an application, the operating system’s loader looks inside the executable image’s header and grabs this subsystem value.

When your entry-point function returns, the startup function calls the C run-time exit function, passing it your return value (nMainRetVal). The exit function does the following:

  • It calls any functions registered by calls to the _onexit function.
  • It calls destructors for all global and static C++ class objects.
  • In DEBUG builds, leaks in the C/C++ run-time memory management are listed by a call to the _CrtDumpMemoryLeaks function if the _CRTDBG_LEAK_CHECK_DF flag has been set.
  • It calls the operating system’s ExitProcess function, passing it nMainRetVal. This causes the operating system to kill your process and set its exit code.

As it turns out, HMODULEs and HINSTANCEs are exactly the same thing. If the documentation for a function indicates that an HMODULE is required, you can pass an HINSTANCE, and vice versa. There are two data types because in 16-bit Windows HMODULEs and HINSTANCEs identified different things.

The base address where an executable file’s image loads is determined by the linker. Different linkers can use different default base addresses. The Visual Studio linker uses a default base address of 0x00400000 for a historical reason: this is the lowest address an executable file image can load to when you run Windows 98. You can change the base address that your application loads to by using the /BASE:address linker switch for Microsoft’s linker.

Keep in mind two important characteristics of the GetModuleHandle function.

  • First, it examines only the calling process’ address space. If the calling process does not use any common dialog functions, calling GetModuleHandle and passing it ComDlg32 causes NULL to be returned even though ComDlg32.dll is probably loaded into the address spaces of other processes.
  • Second, calling GetModuleHandle and passing a value of NULL returns the base address of the executable file in the process’ address space. So even if you call GetModuleHandle(NULL) from code that is contained inside a DLL, the value returned is the executable file’s base address—not the DLL file’s base address.

Below is example code to get a specified environment variable:

PCTSTR
pszVariableName = L“TEMP”;

PTSTR
pszValue = NULL;

// Get the size of the buffer that is required to store the value

DWORD
dwResult = GetEnvironmentVariable(pszVariableName, pszValue, 0);

if (dwResult != 0) {

// Allocate the buffer to store the environment variable value

DWORD
size = dwResult * sizeof(TCHAR);

pszValue = (PTSTR)malloc(size);

GetEnvironmentVariable(pszVariableName, pszValue, size);

_tprintf(TEXT(“%s=%s\n”), pszVariableName, pszValue);

free(pszValue);

} else {

_tprintf(TEXT(“‘%s’=<unknown value>\n”), pszVariableName);

}

Many strings contain replaceable strings within them. For example, I found this string somewhere in the registry:

%USERPROFILE%\Documents

The portion that appears in between percent signs (%) indicates a replaceable string. In this case, the value of the environment variable, USERPROFILE, should be placed in the string. On my machine, the value of my USERPROFILE environment variable is

C:\Users\jrichter

So, after performing the string replacement, the resulting string becomes

C:\Users\jrichter\Documents

Because this type of string replacement is common, Windows offers the ExpandEnvironmentStrings function:

DWORD ExpandEnvironmentStrings(PTCSTR pszSrc, PTSTR pszDst, DWORD chSize);

When you call this function, the pszSrc parameter is the address of the string that contains replaceable environment variable strings. The pszDst parameter is the address of the buffer that will receive the expanded string, and the chSize parameter is the maximum size of this buffer, in characters. The returned value is the size in characters of the buffer needed to store the expanded string. If the chSize parameter is less than this value, the %% variables are not expanded but replaced by empty strings. So you usually call ExpandEnvironmentStrings twice as shown in the following code snippet:

DWORD chValue = ExpandEnvironmentStrings(TEXT(“PATH=’%PATH%'”), NULL, 0);

PTSTR pszBuffer = new TCHAR[chValue];

chValue = ExpandEnvironmentStrings(TEXT(“PATH=’%PATH%'”), pszBuffer, chValue);

_tprintf(TEXT(“%s\r\n”), pszBuffer); delete[] pszBuffer;

Finally, you can use the SetEnvironmentVariable function to add a variable, delete a variable, or modify a variable’s value:

BOOL SetEnvironmentVariable(PCTSTR pszName, PCTSTR pszValue);

This function sets the variable identified by the pszName parameter to the value identified by the pszValue parameter. If a variable with the specified name already exists, SetEnvironmentVariable modifies the value. If the specified variable doesn’t exist, the variable is added and, if pszValue is NULL, the variable is deleted from the environment block.

Normally, threads within a process can execute on any of the CPUs in the host machine. However, a process’ threads can be forced to run on a subset of the available CPUs. This is called processor affinity. Child processes inherit the affinity of their parent processes.

Associated with each process is a set of flags that tells the system how the process should respond to serious errors, which include disk media failures, unhandled exceptions, file-find failures, and data misalignment. A process can tell the system how to handle each of these errors by calling the SetErrorMode function:

UINT SetErrorMode(UINT fuErrorMode);

The fuErrorMode parameter is a combination of any of the flags shown in Table below bitwise ORed together:

When full pathnames are not supplied, the various Windows functions look for files and directories in the current directory of the current drive. For example, if a thread in a process calls CreateFile to open a file (without specifying a full pathname), the system looks for the file in the current drive and directory.

If you call a function, passing a drive-qualified name indicating a drive that is not the current drive, the system looks in the process’ environment block for the variable associated with the specified drive letter. If the variable for the drive exists, the system uses the variable’s value as the current directory. If the variable does not exist, the system assumes that the current directory for the specified drive is its root directory.

You create a process with the CreateProcess function:

BOOL CreateProcess(

PCTSTR pszApplicationName,

PTSTR pszCommandLine,

PSECURITY_ATTRIBUTES psaProcess,

PSECURITY_ATTRIBUTES psaThread,

BOOL bInheritHandles,

DWORD fdwCreate,

PVOID pvEnvironment,

PCTSTR pszCurDir,

PSTARTUPINFO psiStartInfo,

PPROCESS_INFORMATION ppiProcInfo);

Steps to run an application:

  • When a thread calls CreateProcess, the system creates a process kernel object with an initial usage count of 1.
  • The system then creates a virtual address space for the new process and loads the code and data for the executable file and any required DLLs into the process’ address space.
  • The system then creates a thread kernel object (with a usage count of 1) for the new process’ primary thread.
  • This primary thread begins by executing the application entry point set by the linker as the C/C++ run-time startup code, which eventually calls your WinMain, wWinMain, main, or wmain function.
  • If the system successfully creates the new process and primary thread, CreateProcess returns TRUE.

By the way, if you are calling the ANSI version of CreateProcess on Windows Vista, you will not get an access violation because a temporary copy of the command-line string is made. (For more information read “Working with Characters and Strings.”).

Process priority classes affect how the threads contained within the process are scheduled with respect to other processes’ threads.

If you need to pass the two attributes at the same time, don’t forget that the handles associated with PROC_THREAD_ATTRIBUTE_HANDLE_LIST must be valid in the new parent process associated with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS because they will be inherited from this process, not the current process calling CreateProcess.

For some reason, many developers believe that closing the handle to a process or thread forces the system to kill that process or thread. This is absolutely not true. Closing the handle simply tells the system that you are not interested in the process or thread’s statistical data. The process or thread will continue to execute until it terminates on its own.

Task Manager creates this “System Idle Process” as a placeholder for the Idle thread that runs when nothing else is running. The number of threads in the System Idle Process is always equal to the number of CPUs in the machine. As such, it always represents the percentage of CPU usage that is not being used by real processes.

If your application uses IDs to track processes and threads, you must be aware that the system reuses process and thread IDs immediately.

You can discover the ID of the current process by using GetCurrentProcessId and the ID of the running thread by calling GetCurrentThreadId. You can also get the ID of a process given its handle by using GetProcessId and the ID of a thread given its handle by using GetThreadId. Last but not least, from a thread handle, you can determine the ID of its owning process by calling GetProcessIdOfThread.

Occasionally, you’ll work on an application that wants to determine its parent process. The first thing you should know is that a parent-child relationship exists between processes only at the time when the child is spawned. Just before the child process begins executing code, Windows does not consider a parent-child relationship to exist anymore.

A process can be terminated in four ways:

  • The primary thread’s entry-point function returns. (This is highly recommended.)
  • One thread in the process calls the ExitProcess function. (Avoid this method.)
  • A thread in another process calls the TerminateProcess function. (Avoid this method.)
  • All the threads in the process just die on their own. (This hardly ever happens.)

Having your primary thread’s entry-point function return ensures the following:

  • Any C++ objects created by this thread will be destroyed properly using their destructors.
  • The operating system will properly free the memory used by the thread’s stack.
  • The system will set the process’ exit code (maintained in the process kernel object) to your entry-point function’s return value.
  • The system will decrement the process kernel object’s usage count

When your primary thread’s entry-point function (WinMain, wWinMain, main, or wmain) returns, it returns to the C/C++ run-time startup code, which properly cleans up all the C run-time resources used by the process. After the C run-time resources have been freed, the C run-time startup code explicitly calls ExitProcess, passing it the value returned from your entry-point function. This explains why simply returning from your primary thread’s entry-point function terminates the entire process. Note that any other threads running in the process terminate along with the process.

Note that calling ExitProcess or ExitThread causes a process or thread to die while inside a function. As far the operating system is concerned, this is fine and all of the process’ or thread’s operating system resources will be cleaned up perfectly. However, a C/C++ application should avoid calling these functions because the C/C++ run time might not be able to clean up properly. Examine the following code:

 

#include <windows.h>

 

 

#include <stdio.h>

 

 

class CSomeObj {

 

 

public:

 

 

   CSomeObj()  { printf("Constructor\r\n"); }

 

 

   ~CSomeObj() { printf("Destructor\r\n"); }

 

 

};

 

 

CSomeObj g_GlobalObj;

 

 

void main () {

 

 

   CSomeObj LocalObj;

 

 

   ExitProcess(0);    // This shouldn't be here

 

 

   // At the end of this function, the compiler automatically added

 

 

   // the code necessary to call LocalObj's destructor.

 

 

   // ExitProcess prevents it from executing.

 

 

}

 

When the preceding code executes, you’ll see the following:

 

Constructor

 

 

Constructor

 

Two objects are being constructed: a global object and a local object. However, you’ll never see the word Destructor appears. The C++ objects are not properly destructed because ExitProcess forces the process to die on the spot: the C/C++ run time is not given a chance to clean up.

As said, you should never call ExitProcess explicitly. If I remove the call to ExitProcess in the preceding code, running the program yields this:

 

Constructor

 

 

Constructor

 

 

Destructor

 

 

Destructor

 

When a process terminates, the following actions are set in motion:

  • Any remaining threads in the process are terminated.
  • All the User and GDI objects allocated by the process are freed, and all the kernel objects are closed. (These kernel objects are destroyed if no other process has open handles to them. However, the kernel objects are not destroyed if other processes do have open handles to them.)
  • The process’ exit code changes from STILL_ACTIVE to the code passed to ExitProcess or TerminateProcess.
  • The process kernel object’s status becomes signaled. This is why other threads in the system can suspend themselves until the process is terminated.
  • The process kernel object’s usage count is decremented by 1.

When you design an application, you might encounter situations in which you want another block of code to perform work. You assign work like this all the time by calling functions or subroutines. When you call a function, your code cannot continue processing until the function has returned. And in many situations, this single-tasking synchronization is needed. An alternative way to have another block of code perform work is to create a new thread within your process and have it help with the processing. This lets your code continue processing while the other thread performs the work you requested. This technique is useful, but it creates synchronization problems when your thread needs to see the results of the new thread.

Another approach is to spawn off a new process—a child process—to help with the work. To process the work, you simply create a new thread within the same process. You write some code, test it, and get some incorrect results. You might have an error in your algorithm, or maybe you dereferenced something incorrectly and accidentally overwrote something important in your address space. One way to protect your address space while having the work processed is to have a new process perform the work. You can then wait for the new process to terminate before continuing with your own work, or you can continue working while the new process works.

Unfortunately, the new process probably needs to perform operations on data contained in your address space. In this case, it might be a good idea to have the process run in its own address space and simply give it access to the relevant data contained in the parent process’ address space, thus protecting all the data not relevant to the task at hand. Windows offers several methods for transferring data between different processes: Dynamic Data Exchange (DDE), OLE, pipes, mailslots, and so on. One of the most convenient ways to share the data is to use memory-mapped files.

If you want to create a new process, have it do some work, and wait for the result, you can use code similar to the following:

 

PROCESS_INFORMATION pi;

 

 

DWORD dwExitCode;

 

 

// Spawn the child process.

 

 

BOOL fSuccess = CreateProcess(..., &pi);

 

 

if (fSuccess) {

 

 

   // Close the thread handle as soon as it is no longer needed!

 

 

   CloseHandle(pi.hThread);

 

 

   // Suspend our execution until the child has terminated.

 

 

   WaitForSingleObject(pi.hProcess, INFINITE);

 

 

   // The child process terminated; get its exit code.

 

 

   GetExitCodeProcess(pi.hProcess, &dwExitCode);

 

 

   // Close the process handle as soon as it is no longer needed.

 

 

   CloseHandle(pi.hProcess);

 

 

}

 

You’ll notice that in the code fragment, we close the handle to the child process’ primary thread kernel object immediately after CreateProcess returns. This does not cause the child’s primary thread to terminate—it simply decrements the usage count of the child’s primary thread object. Here’s why this practice is a good idea: Suppose that the child process’ primary thread spawns off another thread and then the primary thread terminates. At this point, the system can free the child’s primary thread object from its memory if the parent process doesn’t have an outstanding handle to this thread object. But if the parent process does have a handle to the child’s thread object, the system can’t free the object until the parent process closes the handle.

Most of the time, an application starts another process as a detached process. This means that after the process is created and executing, the parent process doesn’t need to communicate with the new process or doesn’t require it to complete its work before the parent process continues. This is how Windows Explorer works. After Windows Explorer creates a new process for the user, it doesn’t care whether that process continues to live or whether the user terminates it.

To give up all ties to the child process, Windows Explorer must close its handles to the new process and its primary thread by calling CloseHandle. The following code example shows how to create a new process and how to let it run detached:

 

PROCESS_INFORMATION pi;

 

 

// Spawn the child process.

 

 

BOOL fSuccess = CreateProcess(..., &pi);

 

 

if (fSuccess) {

 

 

   // Allow the system to destroy the process & thread kernel

 

 

   // objects as soon as the child process terminates.

 

 

   CloseHandle(pi.hThread);

 

 

   CloseHandle(pi.hProcess);

 

 

}

 

Many people ask why Windows Vista doesn’t just ask once and then let the user’s desire to run a specific application as Administrator be stored in the system so that Windows Vista never asks the user again. Windows Vista doesn’t offer this because it would have to store the data somewhere (like in the registry or in some other file), and if this store ever got compromised, an application could modify the store so that its malware always ran elevated without the user being prompted.

If your application always requires Administrator privileges, such as during an installation step, the operating system can automatically prompt the user for privileges elevation each time your application is invoked. How do the UAC components of Windows decide what to do when a new process is spawned?

If a specific kind of resource (RT_MANIFEST) is found embedded within the application executable, the system looks for the <trustInfo> section and parses its contents. Here is an example of this section in the manifest file:

 

...

 

 

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">

 

 

   <security>

 

 

      <requestedPrivileges>

 

 

         <requestedExecutionLevel

 

 


	level="requireAdministrator"

 

 

         />

 

 

      </requestedPrivileges>

 

 

   </security>

 

 

</trustInfo>

 

 

...

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s