You often need to treat a group of processes as a single entity. For example, when you tell Microsoft Visual Studio to build a C++ project, it spawns Cl.exe, which might have to spawn additional processes (such as the individual passes of the compiler). But if the user wants to prematurely stop the build, Visual Studio must somehow be able to terminate Cl.exe and all its child processes. Solving this simple (and common) problem in Microsoft Windows has been notoriously difficult because Windows doesn’t maintain a parent/child relationship between processes. In particular, child processes continue to execute even after their parent process has been terminated.
Microsoft Windows offers a job kernel object that lets you group processes together and create a “sandbox” that restricts what the processes can do. It is best to think of a job object as a container of processes. However, it is also useful to create jobs that contain a single process because you can place restrictions on that process that you normally cannot.
Be aware that closing a job object does not force all the processes in the job to be terminated. The job object is actually marked for deletion and is destroyed automatically only after all the processes within the job have been terminated.
Note that closing the job’s handle causes the job to be inaccessible to all processes even though the job still exists, as shown in the following code:
// Create a named job object.
hJob = CreateJobObject(NULL, TEXT(“Jeff”));
// Put our own process in the job.
// Closing the job does not kill our process or the job.
// But the name (“Jeff”) is immediately disassociated with the job.
// Try to open the existing job.
hJob = OpenJobObject(JOB_OBJECT_ALL_ACCESS, FALSE, TEXT(“Jeff”));
// OpenJobObject fails and returns NULL here because the name (“Jeff”)
// was disassociated from the job when CloseHandle was called.
// There is no way to get a handle to this job now.
Several types of restrictions on a job:
- The basic limit and extended basic limit prevent processes within a job from monopolizing the system’s resources.
- Basic UI restrictions prevent processes within a job from altering the user interface.
- Security limits prevent processes within a job from accessing secure resources (files, registry subkeys, and so on).
Well, certainly one of the most popular things that you will want to do with a job is kill all the processes within it. Visual Studio doesn’t have an easy way to stop a build that is in progress because it would have to know which processes were spawned from the first process that it spawned. (This is very tricky. Jeff explain how Developer Studio accomplished this in his Win32 Q & A column in the June 1998 issue of Microsoft Systems Journal, readable at http://www.microsoft.com/msj/0698/win320698.aspx.) Maybe future versions of Visual Studio will use jobs instead because the code is a lot easier to write and you can do much more with it.
Notice how you could be tempted to take advantage of functions from psapi.h, such as GetModuleFileNameEx and GetProcessImageFileName, to obtain the full pathname of a process given its process ID. However, the former fails when the job is notified that a new process is created under its constraints because the address space is not fully initialized: the modules are not yet mapped into it. The case of GetProcessImageFileName is interesting because it is able to retrieve the full pathname in that extreme condition, but the obtained syntax is closer to what you see in kernel mode rather than in user mode— for example, \Device\HarddiskVolume1\Windows\System32\notepad.exe instead of C:\Windows\System32\notepad.exe. This is why you should rely on the new QueryFullProcessImageName function, which returns the expected full pathname in all situations.