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.

Getting a Job Number

The next three programs show three individual job management functions. These functions are all included in a single source file, JobMgt.c.

The first, Program 6-4, shows the GetJobNumber function. Notice the use of file locking with a completion handler to unlock the file. This technique protects against exceptions and inadvertent transfers around the unlock call. Such a transfer might be inserted accidentally during code maintenance even if the original program is correct. Also notice how the record past the end of the file is locked in the event that the file needs to be expanded with a new record.

There's also a subtle defect in this function; a code comment identifies it, and Exercise 6-8 suggests a fix.

Program 6-4 JobMgt: Creating New Job Information

/* Job management utility function. */
#include "Everything.h"
#include "JobMgt.h" /* Listed in Appendix A. */
void GetJobMgtFileName (LPTSTR);
LONG GetJobNumber (PROCESS_INFORMATION *pProcessInfo,
LPCTSTR command)
/* Create a job number for the new process, and enter
the new process information into the job database. */
{
HANDLE hJobData, hProcess;
JM_JOB jobRecord;
DWORD jobNumber = 0, nXfer, exitCode, fileSizeLow, fileSizeHigh;
TCHAR jobMgtFileName[MAX_PATH];
OVERLAPPED regionStart;
if (!GetJobMgtFileName (jobMgtFileName)) return -1;
/* Produces "\tmp\UserName.JobMgt" */
hJobData = CreateFile (jobMgtFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hJobData == INVALID_HANDLE_VALUE) return -1;
/* Lock the entire file plus one possible new
record for exclusive access. */
regionStart.Offset = 0;
regionStart.OffsetHigh = 0;
regionStart.hEvent = (HANDLE)0;
/* Find file size: GetFileSizeEx is an alternative */
fileSizeLow = GetFileSize (hJobData, &fileSizeHigh);
LockFileEx (hJobData, LOCKFILE_EXCLUSIVE_LOCK,
0, fileSizeLow + SJM_JOB, 0, &regionStart);
__try {
/* Read records to find empty slot. */
/* See text comments and Exercise 6-8 regarding a potential
defect (and fix) caused by process ID reuse. */
while (ReadFile (hJobData, &jobRecord, SJM_JOB, &nXfer, NULL)
&& (nXfer > 0)) {|
if (jobRecord.ProcessId == 0) break;
hProcess = OpenProcess(PROCESS_ALL_ACCESS,
FALSE, jobRecord.ProcessId);
if (hProcess <== NULL) break;
if (GetExitCodeProcess (hProcess, &exitCode)
&& (exitCode != STILL_ACTIVE)) break;|
jobNumber++;
}
/* Either an empty slot has been found, or we are at end
of file and need to create a new one. */
if (nXfer != 0) /* Not at end of file. Back up. */
SetFilePointer (hJobData, -(LONG)SJM_JOB,
NULL, FILE_CURRENT);
jobRecord.ProcessId = pProcessInfo->dwProcessId;
_tcsnccpy (jobRecord.commandLine, command, MAX_PATH);
WriteFile (hJobData, &jobRecord, SJM_JOB, &nXfer, NULL);
} /* End try. */
__finally {
UnlockFileEx (hJobData, 0, fileSizeLow + SJM_JOB, 0,
&regionStart);
CloseHandle (hJobData);
}
return jobNumber + 1;
}

Listing Background Jobs

Program 6-5 shows the DisplayJobs job management function.

Program 6-5 JobMgt: Displaying Active Jobs

BOOL(DisplayJobs(Void))
/* Scan the job database file, reporting job status. */
{
HANDLE hJobData, hProcess;
JM_JOB jobRecord;
DWORD jobNumber = 0, nXfer, exitCode, fileSizeLow, fileSizeHigh;
TCHAR jobMgtFileName[MAX_PATH];
OVERLAPPED regionStart;
GetJobMgtFileName (jobMgtFileName);
hJobData = CreateFile (jobMgtFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
regionStart.Offset = 0;
regionStart.OffsetHigh = 0;
regionStart.hEvent = (HANDLE)0;
/* Demonstration: GetFileSize instead of GetFileSizeEx */
fileSizeLow = GetFileSize (hJobData, &fileSizeHigh);
LockFileEx (hJobData, LOCKFILE_EXCLUSIVE_LOCK,
0, fileSizeLow, fileSizeHigh, &regionStart);
__try {
while (ReadFile (hJobData, &jobRecord, SJM_JOB, &nXfer, NULL)
&& (nXfer > 0)){
jobNumber++;
if (jobRecord.ProcessId == 0)
continue;
hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE,
jobRecord.ProcessId);
if (hProcess != NULL)
GetExitCodeProcess (hProcess, &exitCode);
_tprintf (_T (" [%d] "), jobNumber);
if (hProcess == NULL)
_tprintf (_T (" Done"));
else if (exitCode != STILL_ACTIVE)
_tprintf (_T ("+ Done"));
else _tprintf (_T (" "));
_tprintf (_T (" %s\n"), jobRecord.commandLine);
/* Remove processes that are no longer in system. */
if (hProcess == NULL) { /* Back up one record. */
SetFilePointer (hJobData, -(LONG)nXfer,
NULL, FILE_CURRENT);
jobRecord.ProcessId = 0;
WriteFile (hJobData, &jobRecord, SJM_JOB, &nXfer, NULL);
}
} /* End of while. */
} /* End of __try. */
__finally {
UnlockFileEx (hJobData, 0, fileSizeLow, fileSizeHigh,
&regionStart);
CloseHandle (hJobData);
}
return TRUE;
}

Finding a Job in the Job List File

Program 6-6 shows the final job management function, FindProcessId, which obtains the process ID of a specified job number. The process ID, in turn, can be used by the calling program to obtain a handle and other process status information.

Program 6-6 JobMgt: Getting the Process ID from a Job Number

DWORD FindProcessId (DWORD jobNumber)
/* Obtain the process ID of the specified job number. */
{
HANDLE hJobData;
JM_JOB jobRecord;
DWORD nXfer;
TCHAR jobMgtFileName[MAX_PATH];
OVERLAPPED regionStart;
/* Open the job management file. */
GetJobMgtFileName (jobMgtFileName);
hJobData = CreateFile (jobMgtFileName, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hJobData == INVALID_HANDLE_VALUE) return 0;
/* Position to the entry for the specified job number.
* The full program assures that jobNumber is in range. */
SetFilePointer (hJobData, SJM_JOB * (jobNumber - 1),
NULL, FILE_BEGIN);
/* Lock and read the record. */
regionStart.Offset = SJM_JOB * (jobNumber - 1);
regionStart.OffsetHigh = 0; /* Assume a "short" file. */
regionStart.hEvent = (HANDLE)0;
LockFileEx (hJobData, 0, 0, SJM_JOB, 0, &regionStart);
ReadFile (hJobData, &jobRecord, SJM_JOB, &nXfer, NULL);
UnlockFileEx (hJobData, 0, SJM_JOB, 0, &regionStart);
CloseHandle (hJobData);
return jobRecord.ProcessId;
}



Run 6-6 JobShell:
Managing Multiple Processes

Run 6-6 shows the job shell managing several jobs using grep, grepMP, and sortBT (Chapter 5). Notes on Run 6-6 include:

  • This run uses the same four 640MB files (l1.txt, etc.) as Run 6-1.
  • You can quit and reenter JobShell and see the same jobs.
  • A "Done" job is listed only once.
  • The grep job uses the -c option, so the results appear in a separate console
    (not shown in the screenshot).
  • JobShell and the grepMP job contend for the main console, so some output
    can overlap, although the problem does not occur in this example.

Job Objects

You can collect processes together into job objects where the processes can be controlled together, and you can specify resource limits for all the job object member processes and maintain accounting information.

The first step is to create an empty job object with CreateJobObject, which takes two arguments, a name and security attributes, and returns a job object handle. There is also an OpenJobObject function to use with a named object. CloseHandle destroys the job object.

AssignProcessToJobObject simply adds a process specified by a process handle to a job object; there are just two parameters. A process cannot be a member of more than one job, so AssignProcessToJobObject fails if the process associated with the handle is already a member of some job. A process that is added to a job inherits all the limits associated with the job and adds its accounting information to the job, such as the processor time used. By default, a new child process created by a process in the job will also belong to the job unless the CREATE_BREAKAWAY_FROM_JOB flag is specified in the dwCreationFlags argument to CreateProcess.

Finally, you can specify control limits on the processes in a job using SetInformationJobObject.

BOOL SetInformationJobObject (
HANDLE hJob,
JOBOBJECTINFOCLASS JobObjectInformationClass,
LPVOID lpJobObjectInformation,
DWORD cbJobObjectInformationLength)

  • hJob is a handle for an existing job object.
  • JobObjectInformationClass specifies the information class for the limits you wish to set. There are five values; JobObjectBasicLimitInformation is one value and is used to specify information such as the total and perprocess time limits, working set size limits,3 limits on the number of active processes, priority, and processor affinity (the processors of a multiprocessor computer that can be used by threads in the job processes).
  • lpJobObjectInformation points to the actual information required by the preceding parameter. There is a different structure for each class.
  • JOBOBJECT_BASIC_ACCOUNTING_INFORMATION allows you to get the total time (user, kernel, and elapsed) of the processes in a job.
  • JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE will terminate all processes in the job object when you close the last handle referring to the object.
  • The last parameter is the length of the preceding structure.


QueryJobInformationObject obtains the current limits. Other information classes impose limits on the user interface, I/O completion ports (see Chapter 14), security, and job termination.

Example: Using Job Objects

Program 6-7, JobObjectShell, illustrates using job objects to limit process execution time and to obtain user time statistics. JobObjectShell is a simple extension of JobShell that adds a command line time limit argument, in seconds. This limit applies to every process that JobObjectShell manages.

When you list the running processes, you will also see the total number of processes and the total user time on a four-processor computer.

Caution: The term "job" is used two ways here, which is confusing. First, the program uses Windows job objects to monitor all the individual processes. Then, borrowing some UNIX terminology, the program also regards each managed process to be a "job."

First, we'll modify the usual order and show Run 6-7, which runs the command:

3 The working set is the set of virtual address space pages that the OS determines must be loaded in memory before any thread in the process is ready to run. This subject is covered in most OS texts.

to limit each process to a minute. The example then runs to shell commands:

timep grep 1234561 l2.txt l3.txt l4.txt
timep grepMP 1234561 l2.txt l3.txt l4.txt

as in Run 6-6. Note how the jobs command counts the processes that timep creates as well as those that grepMP creates to search the files, resulting in seven processes total. There is also a lot of contention for the console, mixing output from several processes, so you might want to run this example with the -c option. There are also a few unexpected results, which are described for further investigation in Exercise 6-12.

Program 6-7 gives the JobObjectShell listing; it's an extension of Job- Shell (Program 6-3), so the listing is shortened to show the new code. There are



Run 6-7 JobObjectShell:
Monitoring Processes with a Job Object

some deviations from the MSDN documentation, which are described in Exercise 6-12 for investigation.

Program 6-7 JobObjectShell: Monitoring Processes with a Job Object

/* Chapter 6 */
/* JobObjectShell.c JobShell extension
Enhances JobShell with a time limit on each process.
The process time limit (seconds) is argv[1] (if present)
0 or omitted means no process time limit
*/
#include "Everything.h"
#include "JobManagement.h"
#define MILLION 1000000
HANDLE hJobObject = NULL;
JOBOBJECT_BASIC_LIMIT_INFORMATION basicLimits =
{0, 0, JOB_OBJECT_LIMIT_PROCESS_TIME};
int _tmain (int argc, LPTSTR argv[])
{
LARGE_INTEGER processTimeLimit;
. . .
hJobObject = NULL;
processTimeLimit.QuadPart = 0;
if (argc >= 2) processTimeLimit.QuadPart = atoi(argv[1]);
basicLimits.PerProcessUserTimeLimit.QuadPart =
processTimeLimit.QuadPart * 10 * MILLION;
hJobObject = CreateJobObject(NULL, NULL);
SetInformationJobObject(hJobObject,
JobObjectBasicLimitInformation, &basicLimits,
sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION));
. . .
/* Process commands. Call Jobbg, Jobs, etc. - listed below */
CloseHandle (hJobObject);
return 0;
}
/* Jobbg: Execute a command line in the background, put
the job identity in the user's job file, and exit.
*/
int Jobbg (int argc, LPTSTR argv[], LPTSTR command)
{
/* Execute the command line (targv) and store the job id,
the process id, and the handle in the jobs file. */
DWORD fCreate;
LONG jobNumber;
BOOL flags[2];
STARTUPINFO startUp;
PROCESS_INFORMATION processInfo;
LPTSTR targv = SkipArg (command);
GetStartupInfo (&startUp);
/* Determine the options. */
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 the job/thread suspended.
Resume it once the job is entered properly. */
CreateProcess (NULL, targv, NULL, NULL, TRUE,
fCreate | CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP,
NULL, NULL, &startUp, &processInfo);
AssignProcessToJobObject(hJobObject, processInfo.hProcess);
jobNumber = GetJobNumber (&processInfo, targv);
                If (jobNumber >= 0) Then
ResumeThread (processInfo.hThread);
else {
TerminateProcess (processInfo.hProcess, 3);
CloseHandle (processInfo.hThread);
CloseHandle (processInfo.hProcess);
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 that have
been created by this user under job management;
that is, have been started with the jobbg command.
List summary process count and user time information.
*/

int Jobs (int argc, LPTSTR argv[], LPTSTR command)
{
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION BasicInfo;
DisplayJobs (); /* Not job objects, but jobbg created processes */
/* Dispaly the job object information */
QueryInformationJobObject(hJobObject,
JobObjectBasicAccountingInformation, &BasicInfo,
sizeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION), NULL);
_tprintf (_T("Total Processes: %d, Active: %d, Terminated: %d.\n"),
BasicInfo.TotalProcesses, BasicInfo.ActiveProcesses,
BasicInfo.TotalTerminatedProcesses);
_tprintf (_T("User time all processes: %d.%03d\n"),
BasicInfo.TotalUserTime.QuadPart / MILLION,
(BasicInfo.TotalUserTime.QuadPart % MILLION) / 10000);
return 0;
}

Total Pages : 7 34567

comments