The Windows Processes and Threads 16

 

 

 

 

 

Job Objects

 

A job object allows groups of processes to be managed as a unit. Job objects are namable, securable, sharable objects that control attributes of the processes associated with them. Operations performed on the job object affect all processes associated with the job object. To create a job object, use the CreateJobObject() function. When the job is created, there are no associated processes. To associate a process with a job, use the AssignProcessToJobObject() function. A process can be associated only with a single job. After you associate a process with a job, the association cannot be broken. By default, processes created using CreateProcess() by a process associated with a job are associated with the job; however, processes created using Win32_Process.Create() are not associated with the job. If a job has the extended limit JOB_OBJECT_LIMIT_BREAKAWAY_OK and a process associated with the job was created with the CREATE_BREAKAWAY_FROM_JOB flag, its child processes are not associated with the job. If the job has the extended limit JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, no child processes are associated with the job.

To determine if a process is running in a job, use the IsProcessInJob() function.

A job can enforce limits on each associated process, such as the working set size, process priority, end-of-job time limit, and so on. To set limits for a job object, use the SetInformationJobObject() function. If a process associated with a job attempts to increase its working set size or process priority, the function calls are silently ignored. The job object also records basic accounting information for all its associated processes, including those that have terminated. To retrieve this accounting information, use the QueryInformationJobObject() function. To terminate all processes currently associated with a job object, use the TerminateJobObject() function. To obtain a handle for an existing job object, use the OpenJobObject() function and specify the name given to the object when it was created. Only named job objects can be opened. To close a job object handle, use the CloseHandle() function. The job is destroyed when its last handle has been closed and all associated processes have been terminated. However, if the job has the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag specified, closing the last job object handle terminates all associated processes and then destroys the job object itself. If a tool is to manage a process tree that uses job objects, both the tool and the members of the process tree must cooperate. Use one of the following options:

 

  1. The tool could use the JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK limit. If the tool uses this limit, it cannot monitor an entire process tree. The tool can monitor only the processes it adds to the job. If these processes create child processes, they are not associated with the job. In this option, child processes can be associated with other job objects.
  2. The tool could use the JOB_OBJECT_LIMIT_BREAKAWAY_OK limit. If the tool uses this limit, it can monitor the entire process tree, except for those processes that any member of the tree explicitly breaks away from the tree. A member of the tree can create a child process in a new job object by calling the CreateProcess() function with the CREATE_BREAKAWAY_FROM_JOB flag, then calling the AssignProcessToJobObject() function. Otherwise, the member must handle cases in which AssignProcessToJobObject() fails. The CREATE_BREAKAWAY_FROM_JOB flag has no effect if the tree is not being monitored by the tool. Therefore, this is the preferred option, but it requires advance knowledge of the processes being monitored.
  3. The tool could prevent breakaways of any kind by setting neither the JOB_OBJECT_LIMIT_BREAKAWAY_OK nor the JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK limit. In this option, the tool can monitor the entire process tree. However, if a child process attempts to associate itself or another child process with a job by calling AssignProcessToJobObject(), the call will fail. If the process was designed to be associated with a specific job, this failure may prevent the process from working properly.

 

User-Mode Scheduling

 

User-mode scheduling (UMS) is a light-weight mechanism that applications can use to schedule their own threads. An application can switch between UMS threads in user mode without involving the system scheduler and regain control of the processor if a UMS thread blocks in the kernel. UMS threads differ from fibers in that each UMS thread has its own thread context instead of sharing the thread context of a single thread. The ability to switch between threads in user mode makes UMS more efficient than thread pools for managing large numbers of short-duration work items that require few system calls.

UMS is recommended for applications with high performance requirements that need to efficiently run many threads concurrently on multiprocessor or multicore systems. To take advantage of UMS, an application must implement a scheduler component that manages the application's UMS threads and determines when they should run. Developers should consider whether their application performance requirements justify the work involved in developing such a component. Applications with moderate performance requirements might be better served by allowing the system scheduler to schedule their threads. UMS is available starting with 64-bit versions of Windows 7 and Windows Server 2008 R2. This feature is not available on 32-bit versions of Windows.

 

UMS Scheduler

 

An application's UMS scheduler is responsible for creating, managing, and deleting UMS threads and determining which UMS thread to run. An application's scheduler performs the following tasks:

 

  1. Creates one UMS scheduler thread for each processor on which the application will run UMS worker threads.
  2. Creates UMS worker threads to perform the work of the application.
  3. Maintains its own ready-thread queue of worker threads that are ready to run, and selects threads to run based on the application's scheduling policies.
  4. Creates and monitors one or more completion lists where the system queues threads after they finish processing in the kernel. These include newly created worker threads and threads previously blocked on a system call that become unblocked.
  5. Provides a scheduler entry point function to handles notifications from the system. The system calls the entry point function when a scheduler thread is created, when a worker thread blocks on a system call, or when a worker thread explicitly yields control.
  6. Performs cleanup tasks for worker threads that have finished running.
  7. Performs an orderly shutdown of the scheduler when requested by the application.

 

UMS Scheduler Thread

 

A UMS scheduler thread is an ordinary thread that has converted itself to UMS by calling the EnterUmsSchedulingMode() function. The system scheduler determines when the UMS scheduler thread runs based on its priority relative to other ready threads. The processor on which the scheduler thread runs is influenced by the thread's affinity, same as for non-UMS threads. The caller of EnterUmsSchedulingMode() specifies a completion list and a UmsSchedulerProc() entry point function to associate with the UMS scheduler thread. The system calls the specified entry point function when it is finished converting the calling thread to UMS. The scheduler entry point function is responsible for determining the appropriate next action for the specified thread. An application might create one UMS scheduler thread for each processor that will be used to run UMS threads. The application might also set the affinity of each UMS scheduler thread for a specific logical processor, which tends to exclude unrelated threads from running on that processor, effectively reserving it for that scheduler thread. Be aware that setting thread affinity in this way can affect overall system performance by starving other processes that may be running on the system.

 

UMS Worker Threads, Thread Contexts, and Completion Lists

 

A UMS worker thread is created by calling CreateRemoteThreadEx() with the PROC_THREAD_ATTRIBUTE_UMS_THREAD attribute and specifying a UMS thread context and a completion list. A UMS thread context represents the UMS thread state of a worker thread and is used to identify the worker thread in UMS function calls. It is created by calling CreateUmsThreadContext(). A completion list is created by calling the CreateUmsCompletionList() function. A completion list receives UMS worker threads that have completed execution in the kernel and are ready to run in user mode. Only the system can queue worker threads to a completion list. New UMS worker threads are automatically queued to the completion list specified when the threads were created. Previously blocked worker threads are also queued to the completion list when they are no longer blocked. Each UMS scheduler thread is associated with a single completion list. However, the same completion list can be associated with any number of UMS scheduler threads, and a scheduler thread can retrieve UMS contexts from any completion list for which it has a pointer. Each completion list has an associated event that is signaled by the system when it queues one or more worker threads to an empty list. The GetUmsCompletionListEvent() function retrieves a handle to the event for a specified completion list. An application can wait on more than one completion list event along with other events that make sense for the application.

 

UMS Scheduler Entry Point Function

 

An application's scheduler entry point function is implemented as a UmsSchedulerProc() function. The system calls the application's scheduler entry point function at the following times:

 

  1. When a non-UMS thread is converted to a UMS scheduler thread by calling EnterUmsSchedulingMode().
  2. When a UMS worker thread calls UmsThreadYield().
  3. When a UMS worker thread blocks on a system service such as a system call or a page fault.

 

The Reason parameter of the UmsSchedulerProc() function specifies the reason that the entry point function was called. If the entry point function was called because a new UMS scheduler thread was created, the SchedulerParam parameter contains data specified by the caller of EnterUmsSchedulingMode(). If the entry point function was called because a UMS worker thread yielded, the SchedulerParam parameter contains data specified by the caller of UmsThreadYield(). If the entry point function was called because a UMS worker thread blocked in the kernel, the SchedulerParam parameter is NULL. The scheduler entry point function is responsible for determining the appropriate next action for the specified thread. For example, if a worker thread is blocked, the scheduler entry point function might run the next available ready UMS worker thread.

When the scheduler entry point function is called, the application's scheduler should attempt to retrieve all of the items in its associated completion list by calling the DequeueUmsCompletionListItems() function. This function retrieves a list of UMS thread contexts that have finished processing in the kernel and are ready to run in user mode. The application's scheduler should not run UMS threads directly from this list because this can cause unpredictable behavior in the application. Instead, the scheduler should retrieve all UMS thread contexts by calling the GetNextUmsListItem() function once for each context, insert the UMS thread contexts in the scheduler’s ready thread queue, and only then run UMS threads from the ready thread queue. If the scheduler does not need to wait on multiple events, it should call DequeueUmsCompletionListItems() with a non-zero timeout parameter so the function waits on the completion list event before returning. If the scheduler does need to wait on multiple completion list events, it should call DequeueUmsCompletionListItems() with a timeout parameter of zero so the function returns immediately, even if the completion list is empty. In this case, the scheduler can wait explicitly on completion list events, for example, by using WaitForMultipleObjects().

 

UMS Thread Execution

 

A newly created UMS worker thread is queued to the specified completion list and does not begin running until the application's UMS scheduler selects it to run. This differs from non-UMS threads, which the system scheduler automatically schedules to run unless the caller explicitly creates the thread suspended. The scheduler runs a worker thread by calling ExecuteUmsThread() with the worker thread's UMS context. A UMS worker thread runs until it yields by calling the UmsThreadYield() function, blocks, or terminates.

 

UMS Best Practices

 

Applications that implement UMS should follow these best practices:

 

  1. The underlying structures for UMS thread contexts are managed by the system and should not be modified directly. Instead, use QueryUmsThreadInformation() and SetUmsThreadInformation() to retrieve and set information about a UMS worker thread.
  2. To help prevent deadlocks, the UMS scheduler thread should not share locks with UMS worker threads. This includes both application-created locks and system locks that are acquired indirectly by operations such as allocating from the heap or loading DLLs. For example, suppose the scheduler runs a UMS worker thread that loads a DLL. The worker thread acquires the loader lock and blocks. The system calls the scheduler entry point function, which runs another worker thread that loads a DLL. This causes a deadlock, because the loader lock is already held and cannot be released until the first thread unblocks. To help avoid this problem, delegate work that might share locks with UMS worker threads to a dedicated UMS worker thread or a non-UMS thread.
  3. UMS is most efficient when most processing is done in user mode. Whenever possible, avoid making system calls in UMS worker threads.
  4. UMS worker threads should not assume the system scheduler is being used. This assumption can have subtle effects; for example, if a thread in the unknown code sets a thread priority or affinity, the UMS scheduler might still override it. Code that assumes the system scheduler is being used may not behave as expected and may break when called by a UMS thread.
  5. The system may need to lock the thread context of a UMS worker thread. For example, a kernel-mode asynchronous procedure call (APC) might change the context of the UMS thread, so the thread context must be locked. If the scheduler tries to execute the UMS thread context while it is locked, the call will fail. This behavior is by design, and the scheduler should be designed to retry access to the UMS thread context.

 

 

 

< Processes & Threads 15 | Win32 Process & Thread Programming | Win32 Programming | Processes & Threads 17 >