Terminating a Process

[Previous] [Next]

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.)

This section discusses all four methods and describes what actually happens when a process ends.

The Primary Thread's Entry-Point Function Returns

You should always design an application so that its process terminates only when your primary thread's entry-point function returns. This is the only way to guarantee that all your primary thread's resources are cleaned up properly.

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's 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.

The ExitProcess Function

A process terminates when one of the threads in the process calls ExitProcess:

 VOID ExitProcess(UINT fuExitCode); 

This function terminates the process and sets the exit code of the process to fuExitCode. ExitProcess doesn't return a value because the process has terminated. If you include any code following the call to ExitProcess, that code will never execute.

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.

The Windows Platform SDK documentation states that a process does not terminate until all its threads terminate. As far as the operating system goes, this statement is true. However, the C/C++ run time imposes a different policy on an application: the C/C++ run-time startup code ensures that the process terminates when your application's primary thread returns from its entry-point function—whether or not other threads are running in the process—by calling ExitProcess. However, if you call ExitThread in your entry-point function instead of calling ExitProcess or simply returning, the primary thread for your application will stop executing but the process will not terminate if at least one other thread in the process is still running.

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's 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 code above executes, you'll see:

 Constructor Constructor 

Two objects are being constructed: a global object and a local object. However, you'll never see the word Destructor appear. 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 I said, you should never call ExitProcess explicitly. If I remove the call to ExitProcess in the code above, running the program yields this:

 Constructor Constructor Destructor Destructor 

By simply allowing the primary thread's entry point function to return, the C/C++ run time can perform its cleanup and properly destruct any and all C++ objects. By the way, this discussion does not apply only to C++ objects. The C/C++ run time does many things on behalf of your process; it is best to allow the run time to clean it up properly.

NOTE
Making explicit calls to ExitProcess and ExitThread is a common problem that causes an application to not clean itself up properly. In the case of ExitThread, the process continues to run but can leak memory or other resources.

The TerminateProcess Function

A call to TerminateProcess also ends a process:

 BOOL TerminateProcess( HANDLE hProcess, UINT fuExitCode); 

This function is different from ExitProcess in one major way: any thread can call TerminateProcess to terminate another process or its own process. The hProcess parameter identifies the handle of the process to be terminated. When the process terminates, its exit code becomes the value you passed as the fuExitCode parameter.

You should use TerminateProcess only if you can't force a process to exit by using another method. The process being terminated is given absolutely no notification that it is dying—the application cannot clean up properly and cannot prevent itself from being killed (except by normal security mechanisms). For example, the process cannot flush any information it might have in memory out to disk.

While it is true that the process will not have a chance to do its own cleanup, the operating system does clean up completely after the process so that no operating system resources remain. This means that all memory used by the process is freed, any open files are closed, all kernel objects have their usage counts decremented, and all User and GDI objects are destroyed.

Once a process terminates (no matter how), the system guarantees that the process will not leave any parts of itself behind. There is absolutely no way of knowing whether that process had ever run. A process will leak absolutely nothing once it has terminated. I hope that this is clear.

NOTE
The TerminateProcess function is asynchronous—that is, it tells the system that you want the process to terminate but the process is not guaranteed to be killed by the time the function returns. So you might want to call WaitForSingleObject (described in Chapter 9) or a similar function, passing the handle of the process if you need to know for sure that the process has terminated.

When All the Threads in the Process Die

If all the threads in a process die (either because they've all called ExitThread or because they've been terminated with TerminateThread), the operating system assumes that there is no reason to keep the process's address space around. This is a fair assumption, since there are no more threads executing any code in the address space. When the system detects that no threads are running any more, it terminates the process. When this happens, the process's exit code is set to the same exit code as the last thread that died.

When a Process Terminates

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

  1. Any remaining threads in the process are terminated.
  2. 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.)
  3. The process's exit code changes from STILL_ACTIVE to the code passed to ExitProcess or TerminateProcess.
  4. The process kernel object's status becomes signaled. (See Chapter 9 for more information about signaling.) Other threads in the system can suspend themselves until the process is terminated.
  5. The process kernel object's usage count is decremented by 1.

Note that a process's kernel object always lives at least as long as the process itself. However, the process kernel object might live well beyond its process. When a process terminates, the system automatically decrements the usage count of its kernel object. If the count goes to 0, no other process has an open handle to the object and the object is destroyed when the process is destroyed.

However, the process kernel object's count will not go to 0 if another process in the system has an open handle to the dying process's kernel object. This usually happens when parent processes forget to close their handle to a child process. This is a feature, not a bug. Remember that the process kernel object maintains statistical information about the process. This information can be useful even after the process has terminated. For example, you might want to know how much CPU time the process required. Or, more likely, you might want to obtain the now-defunct process's exit code by calling GetExitCodeProcess:

 BOOL GetExitCodeProcess( HANDLE hProcess, PDWORD pdwExitCode); 

This function looks into the process kernel object (identified by the hProcess parameter) and extracts the member within the kernel object's data structure that identifies the process's exit code. The exit code value is returned in the DWORD pointed to by the pdwExitCode parameter.

You can call this function at any time. If the process hasn't terminated when GetExitCodeProcess is called, the function fills the DWORD with the STILL_ACTIVE identifier (defined as 0x103). If the process has terminated, the actual exit code value is returned.

You might think that you can write code to determine whether a process has terminated by calling GetExitCodeProcess periodically and checking the exit code. This would work in many situations, but it would be inefficient. I'll explain the proper way to determine when a process has terminated in the next section.

Once again, let me remind you that you should tell the system when you are no longer interested in a process's statistical data by calling CloseHandle. If the process has already terminated, CloseHandle will decrement the count on the kernel object and free it.



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net