FREE BOOK

Chapter 6: Process Management

Posted by Addison Wesley Free Book | Windows Forms February 10, 2010
This chapter explains the basics of process management and also introduces the basic synchronization operations and wait functions that will be important throughout the rest of the book.

Process Execution Times

You can determine the amount of time that a process has consumed (elapsed, kernel, and user times) using the GetProcessTimes function.

BOOL GetProcessTimes (
HANDLE hProcess,
LPFILETIME lpCreationTime,
LPFILETIME lpExitTime,
LPFILETIME lpKernelTime,
LPFILETIME lpUserTime)

The process handle can refer to a process that is still running or to one that has terminated. Elapsed time can be computed by subtracting the creation time from the exit time, as shown in the next example. The FILETIME type is a 64-bit item; create a union with a LARGE_INTEGER to perform the subtraction.

Chapter 3's lsW example showed how to convert and display file times, although the kernel and user times are elapsed times rather than calendar times.

GetThreadTimes is similar and requires a thread handle for a parameter.

Example: Process Execution Times

The next example (Program 6-2) implements the familiar timep (time print) utility that is similar to the UNIX time command (time is supported by the Windows command prompt, so a different name is appropriate). timep prints elapsed (or real), user, and system times.

This program uses GetCommandLine, a Windows function that returns the complete command line as a single string rather than individual argv strings. The program also uses a utility function, SkipArg, to scan the command line and skip past the executable name. SkipArg is in the Examples file.

Program 6-2 timep: Process Times

/* Chapter 6. timep. */
#include "Everything.h"
int _tmain (int argc, LPTSTR argv[])
{
STARTUPINFO startUp;
PROCESS_INFORMATION procInfo;
union { /* Structure required for file time arithmetic. */
LONGLONG li;
FILETIME ft;
} createTime, exitTime, elapsedTime;
FILETIME kernelTime, userTime;
SYSTEMTIME elTiSys, keTiSys, usTiSys, startTimeSys;
LPTSTR targv = SkipArg (GetCommandLine ());
HANDLE hProc;
GetStartupInfo (&startUp);
GetSystemTime (&startTimeSys);
/* Execute the command line; wait for process to complete. */
CreateProcess (NULL, targv, NULL, NULL, TRUE,
NORMAL_PRIORITY_CLASS, NULL, NULL, &startUp, &procInfo);
hProc = procInfo.hProcess;
WaitForSingleObject (hProc, INFINITE);
GetProcessTimes (hProc, &createTime.ft,
&exitTime.ft, &kernelTime, &userTime);
elapsedTime.li = exitTime.li - createTime.li;
FileTimeToSystemTime (&elapsedTime.ft, &elTiSys);
FileTimeToSystemTime (&kernelTime, &keTiSys);
FileTimeToSystemTime (&userTime, &usTiSys);
_tprintf (_T ("Real Time: %02d:%02d:%02d:%03d\n"),
elTiSys.wHour, elTiSys.wMinute, elTiSys.wSecond,
elTiSys.wMilliseconds);
_tprintf (_T ("User Time: %02d:%02d:%02d:%03d\n"),
usTiSys.wHour, usTiSys.wMinute, usTiSys.wSecond,
usTiSys.wMilliseconds);
_tprintf (_T ("Sys Time: %02d:%02d:%02d:%03d\n"),
keTiSys.wHour, keTiSys.wMinute, keTiSys.wSecond,
keTiSys.wMilliseconds);
CloseHandle (procInfo.hThread); CloseHandle (procInfo.hProcess);
CloseHandle (hProc);
return 0;
}

Using the timep Command

timep was useful to compare different programming solutions, such as the various Caesar cipher (cci) and sorting utilities, including cci (Program 2-3) and sortMM (Program 5-5). Appendix C summarizes and briefly analyzes some additional results, and there are other examples throughout the book. Notice that measuring a program such as grepMP (Program 6-1) gives kernel and user times only for the parent process. Job objects, described near the end of this chapter, allow you to collect information on a collection of processes. Run 6-1 and Appendix C show that, on a multiprocessor computer, performance can improve as the separate processes, or more accurately, threads, run on different processors. There can also be performance gains if the files are on different physical drives. On the other hand, you cannot always count on such performance gains; for example, there might be resource contention or disk thrashing that could impact performance negatively.

Generating Console Control Events

Terminating a process can cause problems because the terminated process cannot clean up. SEH does not help because there is no general method for one process to cause an exception in another.1 Console control events, however, allow one process to send a console control signal, or event, to another process in certain limited circumstances. Program 4-5 illustrated how a process can set up a handler to catch such a signal, and the handler could generate an exception. In that example, the user generated a signal from the user interface.

It is possible, then, for a process to generate a signal event in another specified process or set of processes. Recall the CreateProcess creation flag value, CREATE_NEW_PROCESS_GROUP. If this flag is set, the new process ID identifies a group of processes, and the new process is the root of the group. All new processes created by the parent are in this new group until another CreateProcess call uses the CREATE_NEW_PROCESS_GROUP flag.

One process can generate a CTRL_C_EVENT or CTRL_BREAK_EVENT in a specified process group, identifying the group with the root process ID. The target processes must have the same console as that of the process generating the event. In particular, the calling process cannot be created with its own console (using the CREATE_NEW_CONSOLE or DETACHED_PROCESS flag).

Chapter 10 shows an indirect way for one thread to cause an exception in another thread, and the same technique is applicable between threads in different processes.

BOOL GenerateConsoleCtrlEvent (
DWORD dwCtrlEvent,
DWORD dwProcessGroup)

The first parameter, then, must be one of either CTRL_C_EVENT or CTRL- _BREAK_EVENT. The second parameter identifies the process group.

Example: Simple Job Management

UNIX shells provide commands to execute processes in the background and to obtain their current status. This section develops a simple "job shell"2 with a similar set of commands. The commands are as follows.

  • jobbg uses the remaining part of the command line as the command for a new process, or job, but the jobbg command returns immediately rather than waiting for the new process to complete. The new process is optionally given its own console, or is detached, so that it has no console at all. Using a new console avoids console contention with jobbg and other jobs. This approach is similar to running a UNIX command with the & option at the end.
  • jobs lists the current active jobs, giving the job numbers and process IDs. This is similar to the UNIX command of the same name.
  • kill terminates a job. This implementation uses the TerminateProcess function, which, as previously stated, does not provide a clean shutdown. TThere is also an option to send a console control signal.

It is straightforward to create additional commands for operations such asbr> suspending and resuming existing jobs.

Because the shell, which maintains the job list, may terminate, the shell employs a user-specific shared file to contain the process IDs, the command, and related information. In this way, the shell can restart and the job list will still be intact. Furthermore, several shells can run concurrently. You could place this information in the registry rather than in a temporary file (see Exercise 6-9).

Concurrency issues will arise. Several processes, running from separate command prompts, might perform job control simultaneously. The job management functions use file locking (Chapter 3) on the job list file so that a user can invoke

Do not confuse these "jobs" with the Windows job objects described later. The jobs here are managed entirely from the programs developed in this section.

job management from separate shells or processes. Also, Exercise 6-8 identifies a defect caused by job id reuse and suggests a fix.

The full program in the Examples file has a number of additional features, not shown in the listings, such as the ability to take command input from a file. Job- Shell will be the basis for a more general "service shell" in Chapter 13 (Program 13-3). Windows services are background processes, usually servers, that can be controlled with start, stop, pause, and other commands.

Creating a Background Job

Program 6-3 is the job shell that prompts the user for one of three commands and then carries out the command. This program uses a collection of job management functions, which are shown in Programs 6-4, 6-5, and 6-6. Run 6-6 then demonstrates how to use the JobShell system.

Program 6-3 JobShell: Create, List, and Kill Background Jobs

/* Chapter 6. */
/* JobShell.c -- job management commands:
jobbg -- Run a job in the background.
jobs -- List all background jobs.
kill -- Terminate a specified job of job family.
There is an option to generate a console control signal. */

#include "Everything.h"
#include "JobMgt.h"
int _tmain (int argc, LPTSTR argv[])
{
BOOL exitFlag = FALSE;
TCHAR command[MAX_COMMAND_LINE], *pc;
DWORD i, localArgc; /* Local argc. */
TCHAR argstr[MAX_ARG][MAX_COMMAND_LINE];
LPTSTR pArgs[MAX_ARG];
for (i = 0; i < MAX_ARG; i++) pArgs[i] = argstr[i];
/* Prompt user, read command, and execute it. */
_tprintf (_T ("Windows Job Management\n"));
while (!exitFlag) {
_tprintf (_T ("%s"), _T ("JM$"));
_fgetts (command, MAX_COMMAND_LINE, stdin);
pc = strchr (command, '\n');
*pc = '\0';
/* Parse the input to obtain command line for new job. */
GetArgs (command, &localArgc, pArgs); /* See Appendix A. */
CharLower (argstr[0]);
if (_tcscmp (argstr[0], _T ("jobbg")) == 0) {
Jobbg (localArgc, pArgs, command);
}
else if (_tcscmp (argstr[0], _T ("jobs")) == 0) {
Jobs (localArgc, pArgs, command);
}
else if (_tcscmp (argstr[0], _T ("kill")) == 0) {
Kill (localArgc, pArgs, command);
}
else if (_tcscmp (argstr[0], _T ("quit")) == 0) {
exitFlag = TRUE;
}
else _tprintf (_T ("Illegal command. Try again\n"));
}
return 0;
}
/* jobbg [options] command-line [Options are mutually exclusive]
-c: Give the new process a console.
-d: The new process is detached, with no console.
If neither is set, the process shares console with jobbg. */
int Jobbg (int argc, LPTSTR argv[], LPTSTR command)
{
DWORD fCreate;
LONG jobNumber;
BOOL flags[2];
STARTUPINFO startUp;
PROCESS_INFORMATION processInfo;
LPTSTR targv = SkipArg (command);
GetStartupInfo (&startUp);
Options (argc, argv, _T ("cd"), &flags[0], &flags[1], NULL);
/* Skip over the option field as well, if it exists. */
if (argv[1][0] == '-') targv = SkipArg (targv);
fCreate = flags[0] ? CREATE_NEW_CONSOLE :
flags[1] ? DETACHED_PROCESS : 0;
/* Create job/thread suspended. Resume once job entered. */
CreateProcess (NULL, targv, NULL, NULL, TRUE,
fCreate | CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP,
NULL, NULL, &startUp, &processInfo);
/* Create a job number and enter the process ID and handle
into the job "data base." */
jobNumber = GetJobNumber (&processInfo, targv); /* See JobMgt.h */
                            If (jobNumber >= 0) Then
ResumeThread (processInfo.hThread);
else {
TerminateProcess (processInfo.hProcess, 3);
CloseHandle (processInfo.hProcess);
ReportError (_T ("Error: No room in job list."), 0, FALSE);
return 5;
}
CloseHandle (processInfo.hThread);
CloseHandle (processInfo.hProcess);
_tprintf (_T (" [%d] %d\n"), jobNumber, processInfo.dwProcessId);
return 0;
}
/* jobs: List all running or stopped jobs. */
int Jobs (int argc, LPTSTR argv[], LPTSTR command)
{
if (!DisplayJobs ()) return 1; /* See job mgmt functions. */
return 0;
}
/* kill [options] jobNumber
-b Generate a Ctrl-Break
-c Generate a Ctrl-C
Otherwise, terminate the process. */
int Kill (int argc, LPTSTR argv[], LPTSTR command)
{
DWORD ProcessId, jobNumber, iJobNo;
HANDLE hProcess;
BOOL cntrlC, cntrlB;
iJobNo =
Options (argc, argv, _T ("bc"), &cntrlB, &cntrlC, NULL);
/* Find the process ID associated with this job. */
jobNumber = _ttoi (argv[iJobNo]);
ProcessId = FindProcessId (jobNumber); /* See job mgmt. */
hProcess = OpenProcess (PROCESS_TERMINATE, FALSE, ProcessId);
if (hProcess == NULL) { /* Process ID may not be in use. */
ReportError (_T ("Process already terminated.\n"), 0, FALSE);
return 2;
}
                                        If (cntrlB) Then
GenerateConsoleCtrlEvent (CTRL_BREAK_EVENT, ProcessId);
                                        ElseIf (cntrlC) Then
GenerateConsoleCtrlEvent (CTRL_C_EVENT, ProcessId);
                                        Else
TerminateProcess (hProcess, JM_EXIT_CODE);
WaitForSingleObject (hProcess, 5000);
CloseHandle (hProcess);
_tprintf (_T ("Job [%d] terminated or timed out\n"), jobNumber);
return 0;
}

Notice how the jobbg command creates the process in the suspended state and then calls the job management function, GetJobNumber (Program 6-4), to get a new job number and to register the job and its associated process. If the job cannot be registered for any reason, the job's process is terminated immediately. Normally, the job number is generated correctly, and the primary thread is resumed and allowed to run.

Total Pages : 7 34567

comments