Blocking - Voluntary Suspension

We now turn our attention to blocking, which the is mechanism that threads use to give up processor time voluntarily to wait for an event to occur or a resource to become available.

The term voluntary is chosen from the perspective of the scheduler and not necessarily from the application's perspective. In this context voluntary suspension refers to an action taken by a thread to give up its time-slice. This will include direct actions such as waiting on semaphores as well as calling APIs, which for internal reasons need to wait for a resource or an event.

PROCBLOCK and its counterpart PROCRUN are the two kernel routines at the heart of the block/run mechanism. These are callable directly by kernel component and also by Device Drivers and File System Drivers through a small interface layer. Application code only gets to call PROCBLOCK and PROCRUN indirectly through system APIs and in particular through the semaphore APIs.

The block/run mechanism is designed with the following criteria:

This is achieved by having an abstract token, known as the Block ID, associated with the resource or event. The BlockId is passed to PROCBLOCK when a thread blocks. Similarly when another thread wishes to wake threads waiting for a resource or event the BlockId that represents the resource or event is passed to PROCRUN.

In addition to the BlockId, callers of PROCRUN receive a flag that indicates whether all or just the highest priority thread waiting on the BlockId should wake.

This mechanism has shortcomings unless certain constraints are applied:

A workable scheme is implemented by limiting the direct use of PROCBLOCK and PROCRUN to system code, device drivers and file system drivers, all of which have access to the System Arena.

Apart from three special conventions the system and most device drivers use addresses as BlockIds. There are three system defined conventional BlockIds are:

fffe....

fffd.... ffca.... x....... (x=a - f) ........

This scheme could be subverted by device drivers, but in general they will choose to block on addresses of resources they own, which are usually allocated out of the system arena and addressed using a GDT select:offset.

Accountability remains an exposure. For BlockIds that are addresses the owner of the memory that the BlockId points to gives a big clue. For conventional BlockIds we have to do more work. These are discussed in detail later. We will first we look at an example of a BlockId that is an address.

Basic Technique:

The technique for analysing blocked threads is two-pronged:

When a thread blocks its BlockId is stored in the TCB TCBSleepId field. Conveniently, this is formatted by using the .PB KDB and DF command.

Note:

.PB under DF lists non-blocked threads. BlockIds are irrelevant for such threads.

  • PB also attempts to interpret the BlockId. The full details of these are given in the Kernel Debugger and Dump Formatter Command Reference. In addition to classifying the BlockId, .PB examines TCB_SemInfo and TCB_SemDebugAddr.

    For many semaphore originated BlockIds TCB_SemInfo is used to store the address or handle of the user's semaphore that lead to the thread blocking. .PB will attempt to locate a near symbol to the semaphore address and display it.

    Under the kernel Debugger, TCB_SemDebugAddr is used to store the address of the caller to the Semaphore API when the thread blocked. If this field is not 0xffffffff .PB attempts to locate a near symbol to the caller and display it.

    Once we have the BlockId, TCB_SemInfo, and TCB_SemDebugAddr we are able to begin searching for information associated with reason for blocking.

    The next step is to decide whether the BlockId is one of the three special categories or to be treated as an address.


    [Back: Thread Scheduling and Dispatching Topics]
    [Next: Blocking on the Address of a Resource]