Method and system for scheduling the use of a computer system resource using a resource planner and a resource provider6003061Abstract A method and system for scheduling the use of a computer system resource using a resource planner and a resource provider is provided. In a preferred embodiment, a resource is scheduled for use by a plurality of consumer entities. Each consumer entity may request the commitment of a share of the resource. The method and system utilizes representations of resource usage policy, present commitments of shares of the resource, and present commitments of specified amounts of the resource over specified period of time. The method and system first receives a request from a consumer entity for the commitment of a specified share of the resource. In response, the method and system determines whether the specified share of the resource should be committed to the requesting consumer entity. This determination is based on the representations of resource usage policy and present commitments of shares of the resource. If it is determined that the specified share of the resource should be committed to the requesting consumer entity, then the method and system modifies the representation of present commitments of shares of the resource to commit the specified share of the resource to the requesting consumer entity. The method and system then schedules the use of the resource by the plurality of consumer entity based on the modified representation of present commitments of shares of the resource. Claims We claim: Description TECHNICAL FIELD
__________________________________________________________________________
interface IResourceSet: IUnknown {
import "mmtype.idl";
import "actvty.idl";
import "resour.idl";
import "eresst.idl";
SCODE AddAmount([in]IResource *Resource, [in] RESOURCE.sub.-- AMOUNT
Amount,
[out] IResourceSet **pResSetResult);
// Adds a resource and its amount to the set. Adds the resource to
// the set if necessary, otherwise adjusts the amount. Returns a new
// resource set with the addition included; original is unmodifled.
SCODE GetAmount([in] IResource *Resource, [out] RESOURCE.sub.--
AMOUNT
*Amount);
// Gets the amount of a particular resource in the set.
// Returns 0.0 as the amount if the resource is not in the set.
SCODE Add([in] IResourceSet *Addend, [out] IResourceSet **Result);
// Adds the contents of the specifled set to this set and returns
// the result. Resource amounts are added if present in both sets.
// Returns a new resource set with the addition included; original is
// unmodified.
SCODE Subtract([In] IResourceSet * Subtrahend, [out] IResourceSet
**Result);
// Subtracts the contents of the subtrahend from this set. Used
// e.g. to figure out what amount of a set of resources must become
// available to satisfy a request. Returns a new resource set with
// the modification included; original is unmodifled.
SCODE Enumerate([out] IEnumResourceSet **ppenm);
// Returns enumerator for this resource set.
SCODE CheckFree();
// Returns S.sub.-- TRUE if enough of each resource in the set is
free
// to reserve the amount associated with that resource.
SCODE GetFree([out] IResourceSet **Free);
// Returns a resource set with the same resources as this one,
with
// the amount associated with each resource in the set equal to the
// amount currently free.
SCODE Reserve([in] IActivity *Activity, [out] IResourceSet
**Available);
// Tries to reserve each resource in the set, in the amount
// associated with it. Returns S.sub.-- OK if al1 succeeded; the output
// resource set pointer is NULL. Returns S.sub.-- FALSE otherwise; no
// resources are allocated, and the output resource set contains the
// amount currently available of each requested resource.
// Note: Intended for use by the resource planner.
SCODE Testlntersect([in] IResourceSet *IntersectWith);
// Return S.sub.-- TRUE if there are resources in common between
the two
// resource sets S.sub.-- FALSE otherwise.
__________________________________________________________________________
FIG. 5 shows an example in which an activity 60 sends a resource set to the resource planner 62 to request quantities of resources. The resource planner 62 receives the resource set and applies an appropriate policy to determine whether the resources should be granted (step 38 in FIG. 2). If the resources are granted, the activity 60 may use the reserved resources (step 40 in FIG. 2). If, on the other hand, the resources are not granted, the resource planner 62 informs the activity 60 of the quantities of the requested resources that are available, if any (step 42 in FIG. 2). The activity 60 then may determine whether the available resources are acceptable (step 44 in FIG. 2). If such available resources are acceptable, the activity 60 may reformulate its reservation request to conform to the current availability of the resources (step 46 in FIG. 2). The negotiation process then is repeated by continuing execution at step 38 in FIG. 2. If, however, the available resources are not acceptable, the activity terminates. Enforcement of the resource allocations in the preferred embodiment of the present invention is voluntary. It is assumed that an activity will comply with the decision of the resource planner 62 because the activity wishes to exhibit predictable performance. If the resource planner 62 indicates that the resources are not available and an activity decides to proceed anyway, it is likely that the activity will encounter serious problems while executing. The preferred embodiment also provides a mechanism for a resource to make known when a persistent overload condition exists. The resource provider may fail operations that exceed reservations. The resource planner may then force a renegotiation of resource reservations to more equitably distribute resource allocation. In other words, enforcement is voluntary but safety mechanisms are provided to help ensure good behavior. As part of the process of granting resources to an activity 60, the resource planner 62 must contact each of the resource providers to reserve the appropriate portion of the resources that has been granted to the activity. Each resource, by definition, must support the IResource interface. This interface is formally defined as follows:
__________________________________________________________________________
interface IResource: IUnknown {
import "mmtype.idl";
import "actvty.idl";
import "respln.idl";
SCODE Total Amount([out] RESOURCE.sub.-- AMOUNT*Total);
// Get the total amount of a resource.
SCODE Reserve([in] IActivity *Client, [in] RESOURCE.sub.-- AMOUNT
Amount,
[out] RESOURCE.sub.-- AMOUNT *Actual);
// Set the amount reserved of a resource for a particular
client.
// Upon return, return value Will be S.sub.-- TRUE if the resource
amount
// was granted. In this case, Actual Will return the actual amount
// of the resource granted. If the full amount requested could not
// be granted, return value will be FALSE and Actual will return the
// amount of the requested resource that is currently available. If
// the amount reserved is less than the previous reservation then
// some of the reservation will have been given back. The amount can
// be zero, in which case the entire existing reservation is being
// given back.
// Note: This operation intended for use by the resource planner.
SCODE GetReservation([in] IActivity *Client,
[out] RESOURCE.sub.-- AMOUNT *Amount);
// Get the amount of the resource currently reserved for the
client.
SCODE GetUsage([in] IActivity *Client, [out] RESOURCE.sub.-- AMOUNT
*Amount)
// Get the usage of the client since the last call to this
method.
SCODE GetFree([out] RESOURCE.sub.-- AMOUNT *Amount);
// Get the amount of the resource that is not currently reserved
at
// the time of the call.
__________________________________________________________________________
The Reserve() method allows the resource planner 62 to reserve a certain quantity of the resource. Hence, for the example shown in FIG. 5, the resource planner 62 makes calls 72A, 72B, 72C, 72D and 72E to call the Reserve() methods of the IResource interfaces supported by the resource providers 50, 52, 54, 56 and 58, respectively. The present invention preferably enables non-real-time programs that are not aware of the resource planner to utilize managed resources. A default reservation for a relatively small quantity of selected resources, such as the processor resource, is preferably made for activities of non-real-time programs that do not make a reservation on their own behalf. Default reservations are preferably made by the resource providers themselves instead of the resource planner. Further, the capacity of resource providers such as the scheduler often exceeds the total reservations for their resource. Resource providers preferably provide such surplus resource capacity to any activity that attempts to utilize the resource. Also, some activities do not use an amount of a resource as large as the amount of their reservation for the resource. Resource providers preferably also provide this unused resource capacity to any activity that attempts to utilize the resource. The present invention therefore enables non-real-time programs to execute on the same computer system with real-time programs. As can be seen above, the IResource interface includes a TotalAmount() method that allows the resource planner 62 to determine the total amount or capacity of the resource in the resource specific units. The GetReservation() method returns the amount of the resource that is reserved for a particular activity but is specified in the parameters for the call to this method. The IResource interface also includes a GetFree() method that returns a value specifying a current amount of the resource that is available. The GetUsage() method returns the actual usage of the resource by an activity. The above-described approach to requesting resources from a resource planner 62 is actually performed by calling the RequestResources() method that is supported by the IResourcePlanner interface of the resource planner 62. The IResourcePlanner interface is defined as follows.
__________________________________________________________________________
interface IResourcePlanner: IUnknown {
import "mmtype.idl";
import "resset.idl";
import "actvty.idl";
import "actnfy.idl";
//
// Resource plan elements currently consist of a resource set.
// May later be extended to pass additional inforrnation to the planner.
//
typedef IResourceSet *PlanElement;
SCODE RequestResources([in] IActivity *Activity, [in] long
cPlanElements,
[in, size.sub.-- is(cP1anElements)] PlanElement PlanElements[
],
[in] IActivityNotify *NotificationInterface,
[out] long *Granted, [out] IResourceSet **Actual);
// Request resources for an activity. Upon return, if the resources
// could not be granted, Granted will be -1 and Actual will return
// the amounts of the requested resources that are currently
// available. Otherwise, Granted will be the index of the plan element
// corresponding to the resource set that was granted. In this case,
// Actual will return the actual amount of each resource granted.
// The plan elements requested are ordered in decreasing value to the
// application. The activity also supplies its resource notification
// interface pointer to the planner at this time.
SCODE ReleaseResources([in] IActivity *Activity);
// Frees all resources reserved by an activity.
SCODE CreateResourceSet(
[in] IResource *Resource, [in] RESOURCE.sub.--
AMOUNT Amount,
[out] IResourceSet **ResourceSet);
// Creates a resource set. If the resource provided is NULL then
// an empty resource set is returned. If a resource is provided
// then a singleton resource set with the resource and amount
// specifled is returned.
__________________________________________________________________________
The RequestResources() method is passed a resource set and passes out a value that specifies either that the resource reservation for the input resource set was granted or was not granted. If the request was not granted, a resource set is passed out that specifies the amount of available resources of the type asked for. The ReleaseResources() method releases all the resources that are reserved by an activity, and the CreateResourceSet() method creates a resource set. The above discussion has not focused on the policy that is adopted by the resource planner in the preferred embodiment of the present invention. FIG. 6A is a flowchart illustrating the steps that are performed by this policy. Nevertheless, those skilled in the art will appreciate that other policies may be implemented in practicing the present invention. In general, the resource planner implements the policy of the policy module it supports. This resource module may be changed and different embodiments of the present invention may employ different policy modules. Nevertheless, in the preferred embodiment, the policy module is implemented through the IResourcePlannerPolicy interface, which is defined as follows.
__________________________________________________________________________
interface IResourcePlannerPolicy : IUnknown {
import "mmtype.idl";
import "resset.idl";
import "actvty.idl";
SCODE SetImportance([in] IActivity *Activity, [in] IMPORTANCE
Importance);
// Inform the resource planner that the indicated activity has
// transitioned to the specified importance. This may trigger a
// resource negotiation.
SCODE GetImportance([in] IActivity *Activity, [out] IMPORTANCE
*Importance);
// Get the current importance level for an activity.
SCODE OnOverload([in] IActivity *Activity,
[in] IResource *Overloaded, [in] RESOURCE.sub.-- AMOUNT
AmountUsed);
// Tell resource planner that an activity has consistently used
more
// of a resource than it has reserved.
__________________________________________________________________________
Initially, the resource planner receives a request for resources from an activity (step 74 in FIG. 6A). The resource planner then checks whether the requested resources are currently available in the requested amounts (step 76 in FIG. 6A). If the resources are available in the requested amounts, the resources are granted to the activity (step 78 in FIG. 6A). If the resources are not all available in the requested quantities, the resource planner checks whether any lower importance activities are using resources that are requested so that the resources may be reassigned to complete the resource reservation of the requesting activity (step 80 in FIG. 6A). The policy of the preferred embodiment in the present invention employs the notion of importance where activities can be ranked according to importance, and the importance of activities may be compared. If lower importance activities are using sought resources that have been requested by the higher importance requesting activity, these resources are reassigned to be granted to the higher importance requesting activity in order to completely satisfy the resource reservation request (step 82 in FIG. 6A). The reassignment of resource reservations is realized by either direct resource provider activity or by prompting renegotiations by lower importance activities. FIG. 6B is a flowchart illustrating the steps that are performed in performing step 82 of FIG. 6A. Typically, the resource planner notifies the lower importance activity that another activity needs some of the resources it is using more than the informed activity does (step 75 in FIG. 6B). The resource planner performs such notification by calling a method on the lActivityNotify interface which each activity supports. This interface is formally defined as follows.
__________________________________________________________________________
interface IActivityNotify: IUnknown {
import "mmtype.idl";
import "resset.idl";
import "actvty.idl";
SCODE OnOverload([in] IActivity *Activity,
[in] IResource *Overloaded, [in] RESOURCE.sub.-- AMOUNT
AmountUsed);
// Tell an activity that it has consistently used more of a
// resource than it has reserved.
SCODE OnNeed([in] IActivity *Activity, [in] IResourceSet *Shortage);
// Tell an activity that other activities need a set of resources
// more than it does. The Shortage resource set says which resources
// are needed, and how much.
SCODE OnAvailable([in] IActivity *Activity, [in] IResourceSet
*Available);
// Tell an activity that additional resources have become available
// that it may wish to negotiate for. The Available resource set
// says which additional resources are now available, and how much.
}
__________________________________________________________________________
The resource planner calls the OnNeed() method to inform the lower importance activity of the quantity of resources that are reserved by that activity are needed by other activities. The lower importance activity then resubmits a new reservation request relinquishing the resources that are needed by the higher importance activity (step 77 in FIG. 6B). Alternatively, the resource planner may directly intervene when activities are poorly behaved. In such an instance, a time-out occurs (step 79 in FIG. 6B), and the resource planner directly calls resource providers on behalf of an activity to change the resource reservations for the activity (step 81 in FIG. 6B). Specifically, the resource planner calls the Reserve() method to alter the resource reservations of the lower importance activities that are poorly behaved to relinquish the resources for use by the higher importance activities. If there are no lower importance activities using the resources that have been requested but are unavailable, the resource reservation request is denied (step 84 in FIG. 6A). The GetImportance() method is used to determine the importance of activities in the above-described process. The preferred embodiment of the present invention assumes that resource reservations do not need to be frequently renegotiated. Nevertheless, in order to provide flexibility and adaptability to changing resource needs, the preferred embodiment facilitates the renegotiation of resource reservations. A number of different events may trigger such renegotiation. This dynamic feedback helps to keep the resource management mechanism self-aware. For example, the changing resource needs of an activity may trigger a renegotiation. FIG. 7A shows a flowchart of the steps that are performed when an activity changes its mode of execution such that its resource needs change substantially enough to warrant renegotiation. Initially, the activity resource needs change enough to warrant renegotiation (step 86 in FIG. 7A). The activity then contacts the resource planner to request renegotiation change the activity's resource reservations (step 88 in FIG. 7A). One example of such a contact is a new call to RequestResources() specifying a different set of resource amounts than currently granted. The resource planner then performs renegotiation with the activity to change the resource reservation granted to the activity (step 90 in FIG. 7A). Resource reservation renegotiation may be also triggered by the actions of other programs. For example, if other programs begin executing, cease executing or substantially change their resource usage, resource availability may change substantially enough to warrant resource reservation renegotiation. As shown in FIG. 7B, events relating to another program change resource usage (step 92). The resource planner then contacts a selected activity to request a modification of the activity's resource usages (step 94 in FIG. 7B). For example, the resource planner may call the OnNeed() method of an activity to indicate that the activity needs to relinquish resources or the resource planner may call the OnAvailable() method to cause an activity to seek more resources. Further, policy may change, causing the SetImportance() method to be called, which may trigger renegotiation. Resource reservations are then renegotiated using the negotiation process described above relative to FIG. 2 (step 96 in FIG. 7B). A final type of event that may trigger resource reservation renegotiation arises when a resource provider experiences persistent overload. FIG. 7C is a flowchart that illustrates the steps that are performed in such an instance. Initially, the resource provider detects a persistent overload condition (step 98 in FIG. 7C). The resource provider then contacts the resource planner to inform the resource planner of the persistent overload condition (step 100 in FIG. 7C). The resource planner may inform an activity that it has consistently overused a resource and initiate a renegotiation by calling the OnOverload() method of the activity. The renegotiation process is subsequently performed (step 102 in FIG. 7C). The above-described examples have dealt with instances where activities request local resources. The preferred embodiment of the present invention also enables activities to request remote resources. This capability is in large part realized by maintaining separate but cooperating resource planners on each computer system within a distributed environment. For example, as shown in FIG. 8, each of the computer systems 104 in the illustrated distributed environment includes its own resource planner 106 that is responsible for managing the resources that are local to the computer systems 104. Each computer system 104 includes local resource providers 108 that are associated with local resources and managed by the local resource planner 106. FIG. 9 is a flowchart illustrating the steps that are performed when an activity requests a resource reservation for a remote resource. The process begins with a local activity requesting a reservation for a remote resource (step 110 in FIG. 9). This may occur, for instance, when a resource set that contains references to some remote resources (as well as possibly some local resources) is passed a RequestResources() call to the local resource planner. Such sets may result from resource query calls to modules implemented by remote objects or via remote procedure calls. The local resource planner receives the request and forwards the request to the remote resource planner for the machine on which the remote resource is found (step 112 in FIG. 9). The remote resource planner processes the request and sends a response back to the local resource planner (step 114 in FIG. 9). The local resource planner receives the response and forwards it to the requesting activity (step 116 in FIG. 9). The present invention is particularly useful in a networked environment, like the Internet, when data is to be transferred over the network. The preferred embodiment of the present invention ensures that the transmission of data, such as audio data or video data, that have real-time delivery requirements are delivered in a timely fashion. Specifically, the preferred embodiment of the present invention ensures that the resources that are required to ensure timely delivery of the data are guaranteed to the activity that is associated with delivering the data. lience, the preferred embodiment to the present invention guarantees the fidelity of application programs in a distributed environment. As discussed above, each application is preferably able to define one or more "activities" that correspond to one or more different behaviors of an application. Examples of behaviors that may be regarded as separate activities are playing a video stream, recording voice input, and tracking the movement of a pointing device. Each activity in turn, is preferably comprised of one or more threads. An application preferably calls a CreateActivity API supported by the resource planner to define each activity of the application. In each case, the CreateActivity API creates an activity object that represents the activity and stores the identities of the threads belonging to the activity. The CreateActivity API returns to the application a pointer to the activity object, which identifies the activity. An application program subsequently creates a new thread by calling a CreateThread API. The CreateThread API takes as an in parameter a pointer to the activity object representing the activity to which the thread shall belong, and stores the identity of the created thread in the activity object pointed to by the in parameter, in addition to actually creating the thread. For example, if an activity for recording voice input utilized one thread for collecting voice input and another thread for storing the collected voice input, the application would first call the CreateActivity API to create an activity object representing the voice input recording activity. This application would subsequently call the CreateThread API twice with the pointer to the activity object returned by the CreateActivity API to create the voice input collection thread and the voice input storage thread. As is also discussed above, once an application creates an activity, it can obtain a reservation for a resource set from the resource planner for the activity by calling the Reserve method of the resource planner with a pointer to the activity object representing the activity for which a resource set is to be reserved. When the resource planner approves such a request, the resource planner conveys to the resource providers for the resources reserved as part of the resource set the amount of its resource that has been reserved and the pointer to the activity for which it has been reserved. Many resource provider are able to provide a resource on a per-activity basis using only this conveyed information. In other cases in which a resource provider, such as the scheduler that is the resource provider for the processor resource, cannot provide its resource on a per-activity basis and must instead provide its resource on a per-thread basis, the resource provider uses the pointer to the activity conveyed by the resource planner to retrieve the identity of the threads that belong to the activity from the activity object representing the activity. The resource provider then uses the identity of these threads to distribute the amount of the activity's reservation for the per-thread resource among the threads belonging to the activity. FIG. 10 is a high-level block diagram of the general-purpose computer system upon which the scheduler preferably operates. The computer system 100 contains one or more central processing units (CPU) 1010, input/output devices 1020, and a computer memory (memory) 1030. Among the input/output devices 1020 is a storage device 1021, such as a hard disk drive; a display device 1022, such as a video monitor; a keyboard 1023; a pointing device 1024, such as a mouse; and a network connection 1025, through which the computer system 1000 may communicate with other connected computer systems (not shown). The memory 1030 preferably contains an operating system 1031, which preferably executes on the CPU 1010 and includes the thread scheduling facility (the scheduler) 1032. The memory 1030 further contains a scheduling status data structure 1033 used by the scheduler 1032, as well as additional programs such as programs 1034 and 1035 whose threads are executed by the computer system. While the scheduler is preferably implemented on a computer system configured as described above, one skilled in the art will recognize that it may also be implemented on computer systems having different configurations. When each thread is created, the scheduler generally receives a time-general scheduling constraint identifying the thread and specifying an overall percentage of processing time to be committed to the thread (or "CPU reservation"). Programs that were developed to support a scheduler utilizing time-general scheduling constraints generally submit a time-general scheduling constraint for each of their threads. In some cases, such programs submit a single time-general scheduling constraint specifying a single CPU reservation for all of its threads to a central resource manager program, which passes this information to the scheduler. The scheduler then assigns individual threads within the program CPU reservations out of the program's reservations, for instance, by evenly dividing the program reservation among the program's threads. For programs that were not developed to support a scheduler utilizing time-general scheduling constraints, the operating system (or whatever entity is used within the computer system to launch such programs) preferably submits a time-general scheduling constraint for each of the program's threads. A thread may preferably submit updated time-general scheduling constraints specifying new CPU reservations at any time during its execution. Subsequent to receiving a time-general scheduling constraint for a thread, if the program that created the thread was developed to support a scheduler utilizing time-specific scheduling constraints, the scheduler may receive one or more time-specific scheduling constraints for the thread. Such time-specific scheduling constraints are preferably submitted to the scheduler by calling a BeginConstraint API supported by the scheduler, and each identify the thread for which they are submitted and specify, for a block of code about to be executed by that thread, a deadline by which the block of code must complete execution ("deadline"), an estimate of the amount of dedicated processor time the block of code will require to complete execution ("estimate"), an indication of whether the thread is critical ("criticality"), i.e., whether failure to complete the execution of the block of code would be unacceptable to the user, and a start time before which the thread should not be further executed. The scheduler may decline a time-specific scheduling constraint where it would be impossible to complete the time-specific scheduling constraint within the bounds of the thread's time-general scheduling constraint, or, less restrictively, where it would be impossible to complete the time-specific scheduling constraint utilizing a larger percentage of processor time than is specified by the CPU reservation of the thread's time-general scheduling constraint. A thread that submits a time-specific scheduling constraint is expected to withdraw the time-specific scheduling constraint when the block of code to which it corresponds completes execution. A thread preferably withdraws a time-specific scheduling constraint by calling an EndConstraint API supported by the scheduler. In a preferred embodiment, the scheduler autonomously withdraws time-specific scheduling constraints when the processor time expended on them significantly exceeds their estimates. FIGS. 11-16 illustrate the process of submitting constraints to the scheduler. FIG. 11 is a data structure diagram showing the creation and modification of a thread data structure in response to receiving a time-general and a time-specific time constraint for a sample thread. FIGS. 12 and 13, discussed in detail below, show the steps preferably performed by the scheduler to process new time-general and time-specific time constraints, respectively. FIG. 11 shows a time-general scheduling constraint 1110 submitted to the scheduler for a sample thread. The sample thread preferably submits the time-general scheduling constraint on its own behalf by calling the BeginConstraint API. The time-general scheduling constraint 1110 specifies a thread identifier ("thread i.d.") 1111 identifying the sample thread (in the case of the sample thread, "A") and a CPU reservation 1112 representing an overall percentage of processing time to be committed to the thread (in the case of the sample thread, 30%). In response to receiving the time-general scheduling constraint 1110, the scheduler creates a thread data structure 1120. FIG. 12 is a flow diagram showing the steps preferably performed by the scheduler to create a thread data structure when the scheduler receives a time-general scheduling constraint. In step 1201, the scheduler creates a new thread data structure such as thread data structure 1220 (FIG. 11). As is discussed in greater detail below, the new thread data structure is created on a list of data structures corresponding to threads that are ready to be executed ("the ready list"), which forms a part of a scheduling status data structure used by the scheduler to maintain the status of each thread for scheduling purposes. In step 1202, the scheduler copies the thread identifier of the time-general scheduling constraint (such as thread identifier 1111) to the thread identifier of the created thread data structure (such as thread identifier 1121). In step 1203, the scheduler creates an urgency data structure (such as urgency data structure 1130) and pushes it on an individual urgency stack of the created thread data structure (such as individual urgency stack 1122). The individual urgency stack of the created thread data structure is a stack of one or more urgency data structures each corresponding to a scheduling constraint submitted by the thread. The most recently stored ("pushed") urgency data structure is said to be the "top" urgency data structure on the individual urgency stack and is used by the scheduler, along with any inherited urgencies, to direct the scheduling of the thread. When the scheduling constraint represented by the top urgency data structure on the individual urgency stack is withdrawn, the top urgency data structure is removed ("popped") from the stack, and the next-most recently pushed urgency data structure becomes the top urgency data structure and is used by the facility, along with any inherited urgencies, to direct the scheduling of the thread. The created urgency data structure specifies a restart time (such as restart time 1131) and a criticality (such as criticality 1132). In step 1204, the scheduler sets the components of the created urgency data structure. The scheduler preferably sets the restart time to a time given by the expression (current time)+((CPU reservation enforcement period).times.(1-(CPU reservation))), where (current time) is the time at which the steps are being performed (here 0); (CPU reservation enforcement period) is a constant period of time over which CPU reservations specified in time-general scheduling constraints are observed by the scheduler, preferably set at or near 1 millisecond (ms) or 1000 microseconds (.mu.s); and (CPU reservation) is the CPU reservation value of the thread data structure (such as CPU reservation 1123, 30% or 0.3). For the sample thread, therefore, the restart time 1131 is (0+1000.times.(1-0.3)) .mu.s=700 .mu.s. The scheduler preferably sets the criticality (such as criticality 1132) of the urgency data structure to not critical ("no"), since time-general scheduling constraints are not generally considered to be critical by the scheduler. The thread data structure 1120 further includes an inherited urgency component 1123, which preferably contains a reference to the effective urgency component (discussed below) of the thread data structures for any threads from which the thread inherits urgency. For clarity, however, the thread identifiers of threads from which this thread inherits urgency are shown instead of such references. The thread data structure 1120 further includes a cached effective urgency component, and which is cached the highest urgency that applies to the thread. The cached effective urgency component is comprised of a restart time and a criticality, and is identified by determining the highest urgency represented by the top individual urgency data structure 1130 and the cached effective urgencies of any threads from which this thread inherits urgency. The cached effective urgency is preferably invalidated and recalculated when the highest urgency among the individual and inherited urgencies changes. In step 1105, the scheduler copies the new top urgency data structure to the cached effective urgency (such as cached effective urgency 1124). The steps shown in FIG. 12 then conclude. The thread data structure 1120 further contains a blocked-on resources component 1126 and an owned resources component 1127, which identify resources that are blocked-on and owned by the thread, respectively. FIG. 11 further shows a time-specific scheduling constraint 1140 submitted to the scheduler for the sample thread. The time-specific scheduling constraint 1140 specifies a thread identifier 1141 identifying the sample thread (in the case of the sample thread, "A"), a deadline 1142 (in the case of the sample thread, 900 .mu.s), an estimate 1143 (in the case of the sample thread, 600 .mu.s), a criticality 1144 (in the case of the sample thread, "no"), and optionally, a start time 1145 before which the thread should not be further executed. In response to receiving the time-specific scheduling constraint 1140, the scheduler modifies thread data structure 1120 to create thread data structure 1120'. FIG. 13 is a flow diagram showing the steps preferably performed by the scheduler to modify a thread data structure when the scheduler receives a time-specific scheduling constraint. In step 1301, if the new time-specific constraint is impossible to complete, then the scheduler returns failure for the submission of the time-specific scheduling constraint, else the scheduler continues in step 1302. In a first preferred embodiment, the scheduler makes the determination in step 1301 by determining whether it would be impossible to complete the time-specific scheduling constraint within the bounds of the thread's time-general scheduling constraint. For example, if a time-specific scheduling constraint that would require 70% of the processing time before its deadline is submitted for a thread having a 15% CPU reservation, the time-specific scheduling constraint is impossible to complete within the thread's time-general scheduling constraint. In a second preferred embodiment, the scheduler makes the determination in step 1301 by determining whether it would be impossible to complete the time-specific scheduling constraint utilizing a larger percentage of processor time than is specified by the CPU reservation of the thread's time-general scheduling constraint. In step 1302, the scheduler pushes a new urgency structure (such as new urgency structure 1150) onto the top of the individual urgency stack (such as individual urgency stack 222'). In step 1303, the scheduler sets the restart time (such as restart time 1151) of the new top urgency structure (such as the new top urgency structure 1150) to a time given by the expression (deadline)-(estimate). For the sample thread, therefore, the restart time 1151 is (900-600) .mu.s=300 .mu.s. In step 1304, the scheduler sets the criticality (such as criticality 1152) of the new top urgency data structure (such as the new top urgency data structure 1150) to the criticality (such as criticality 1144) of the time-specific scheduling constraint (such as time-specific scheduling constraint 1140). In step 1305, if the urgency represented by the new top urgency data structure is higher than the cached effective urgency for the thread, then the scheduler continues at step 1306 to replace the cached effective urgency with the new top urgency data structure. In step 1307, if a start time is specified in the time-specific scheduling constraint, then the scheduler continues at step 1308. In step 1308, the scheduler moves the thread data structure for this thread to a sleeping list, which contains any thread that has made an implicit or explicit request to "sleep," or not be scheduled for execution, until a future time. In step 1308, the scheduler further sets the time at which the thread will be woken and moved from the sleeping list to the ready list as the start time specified in the time-specific scheduling constraint. The scheduler then returns success for the submission of the time-specific scheduling constraint, and the steps shown in FIG. 13 conclude. When the scheduler receives multiple time-specific scheduling constraints for the same thread, it nests urgency data structures corresponding to each time-specific scheduling constraint on the individual urgency stack for the thread. FIG. 14 is a flow diagram showing the steps preferably performed by the scheduler to withdraw a time-specific scheduling constraint when the scheduler receives a request to do so. A thread submits such a request when it completes the execution of the block of code to which the time-specific scheduling constraint corresponds. In step 1401, the scheduler pops the top urgency data structure off of the individual urgency stack for the thread identified in the request to withdraw the time-specific scheduling constraint. In step 1402, if the new top urgency data structure on the individual urgency stack is also the bottom urgency data structure on the individual urgency stack, and is therefore the urgency data structure corresponding to the time-general scheduling constraint, then the scheduler continues in step 1403, else the new top urgency data structure corresponds to a time-specific scheduling constraint and the scheduler continues in step 1404. In step 1403, the scheduler increments the restart time of the new top urgency structure (corresponding to the time-general scheduling constraint) by the time executed for the old top urgency data structure divided by the CPU reservation for the thread. In step 1404, the scheduler increments the restart time of the new top urgency structure (corresponding to a time-specific scheduling constraint) by the time executed for the old top urgency data structure. In step 1405, the scheduler replaces the cached effective urgency of the thread with the highest urgency among the thread's new top urgency data structure and the inherited effective urgencies. After step 1405, the steps shown in FIG. 14 conclude. The facility equitably schedules both threads for which time-specific scheduling constraints have been submitted and threads for which time-specific scheduling constraints have not been submitted. FIGS. 15A-15M are data structure diagrams showing the contents of the scheduling status data structure at various times during the scheduling of two sample threads. FIG. 15A is a data structure diagram showing the initial contents of the scheduling status data structure. The scheduling status data structure is comprised of three linked lists of thread data structures: a processor list 1501 containing one thread that presently being executed on the processor; a blocked list 1503 containing the threads that are blocked on one or more resources; and a ready list 1502 containing any thread not on the processor list or the blocked list, i.e., any thread that is ready to execute but not currently executing. The scheduling status data structure also preferably contains a sleeping list (now shown) for containing threads that have requested to sleep until a specified future wake-up time. At the specified future wake-up time, the scheduler preferably moves such a thread from the sleeping list to the ready list. In computer systems in which the scheduler is scheduling multiple processors, the scheduler preferably maintains one processor list like processor list 1501 for each such processor. For n such processors, the processor lists together contain the n most urgent ready threads. The thread data structures shown on lists 1501, 1502, and 1503 and their urgency data structures are abbreviated to show only their relevant components. The diagram shows that the thread data structure 1510 for thread A is on the processor list, and the thread data structure 1520 for thread B is on the ready list. Thread A has a CPU reservation of 30%, does not have any inherited urgencies, and is operating under a time-specific scheduling constraint. Its restart time is 300 .mu.s, and it is not critical. Thread B has a higher CPU reservation of 50%, it does not have any inherited urgencies, its restart time is 500 .mu.s, and it is not critical. A current time indication 1530 shows that FIG. 15A represents the contents of the scheduling status data structure at time 0 .mu.s. FIG. 16 is a flow diagram showing the steps preferably performed by the scheduler to update the scheduling status data structure. The scheduler preferably performs the steps shown in FIG. 16 when an event occurs that could make a thread a thread ready or increase its urgency. Such events include the submission or withdrawal of a scheduling constraint for a new or existing thread and the release of a resource, including the signaling of a condition. In cases in which the scheduler is scheduling multiple processors, the steps shown in FIG. 16 are performed at different times for each such processor. In cases in which the scheduler is scheduling multiple processors and an event that could make a thread ready occurs on a processor other than the processor that is executing the lowest-urgency thread, however, instead of it self-performing the steps shown in FIG. 16, the processor on which the event occurs preferably directs an inter-processor interrupt to the processor that it is executing the lowest urgency thread, causing the processor that is executing the lowest urgency thread to perform the steps shown in FIG. 16. In step 1601, the scheduler recalculates the restart time for the thread on the processor list. If the top urgency data structure on the individual urgency stack for the thread on the processor list is also the bottom urgency data structure on the individual urgency stack, this urgency data structure corresponds to the thread's time-general scheduling constraint, and its restart time is incremented in step 1601 by a time given by the expression (time executed)/(CPU reservation), where (time executed) is the time that the thread executed since the last time its restart time was recalculated, and where (CPU reservation) is the CPU reservation value of the thread. If, on the other hand, the top urgency data structure on the individual urgency stack for the thread on the processor list is not also the bottom urgency data structure on the individual urgency stack, this top urgency data structure corresponds to a time-specific scheduling constraint, and the thread's restart time is incremented in step 1601 by a time given by the expression (time executed). In step 1602, if the earliest restart time among the threads on the ready list is earlier than the restart time of the thread on the processor list, then the scheduler continues at step 1603, else the scheduler continues at step 1604. In step 1603, the scheduler moves the thread on the processor list to the ready list, and moves the thread having the earliest restart time on the ready list to the processor list. In step 1604, the scheduler sets a custom interrupt to interrupt the execution of the thread on the processor list to allow a different thread to be executed. The scheduler sets the custom interrupt for the later of: (1) the time at which after the restart time of the thread on the processor will exceed the next earliest restart time among the threads on the ready list (2) the current time plus a minimum execution period. The minimum execution period is preferably chosen short enough so that the ready threads all execute within a reasonably short period of time and long enough to minimize the processor time expended on executing the steps shown in FIG. 16. The minimum execution period is therefore preferably chosen at or near 100 .mu.s. In step 1605, the scheduler executes the thread on the processor list. The steps shown in FIG. 16 then conclude. While the thread on the processor list is executing, the custom interrupt could occur, causing the scheduler to again perform the steps shown in FIG. 16. Alternatively, the thread on the processor list could block on or release a resource, causing the scheduler to perform the steps shown in FIGS. 19 and 20 respectively, which are discussed in greater detail below. Because the restart time of thread A, 300 .mu.s, is earlier than the restart time of thread B, 500 .mu.s, the scheduler retains thread A on the processor list and sets a custom interrupt for the time at which the restart time of thread A will be later than the restart time of thread B. Because thread A is operating under a time-specific scheduling constraint, after it is executed, the scheduler increments its restart time by (time executed). In order to advance thread A's restart time to 500 .mu.s, the scheduler executes thread A for 200 .mu.s (target restart time 500 .mu.s -current restart time 300 .mu.s). The scheduler therefore sets a custom interrupt for 200 .mu.s in the future, or t=200 .mu.s (t=current time 0 .mu.s+200 .mu.s). FIG. 15B is a data structure diagram showing the scheduling status data structure after the scheduler has executed thread A for 200 .mu.s. FIG. 15B shows that, after executing thread A for 200 .mu.s, the scheduler has incremented thread A's restart time by 200 .mu.s to 500 .mu.s. FIG. 17 is a timing graph showing the time executed of the threads whose scheduling is shown in FIGS. 15A-15M. The graph shows time executed on the vertical axis versus time elapsed on the horizontal axis for threads A and B. While a thread is being executed (i.e., on the processor list), its time executed grows at the same rate as time elapsed. For example, while thread A is executing during the period of t=0 to 200 .mu.s, its time executed increases from 0 .mu.s to 200 .mu.s. While a thread is not being executed (e.g., on the ready list), its time executed remains constant. For example, while thread A is not being executed during the period of t=200 to 300 .mu.s, its time executed remains at 200 .mu.s. The graph conveys time executed data from the scheduling example shown in FIGS. 15A-15M in this manner as an overview of the example. FIG. 15C is a data structure diagram showing the scheduling status data structure after the scheduler has moved thread B from the ready list to the processor list and thread A from the processor list to the ready list. FIG. 15D is a data structure diagram showing the scheduling status data structure after the scheduler has executed thread B for the minimum execution period, 100 .mu.s (at t=200 .mu.s setting a custom interrupt for t=300 .mu.s). At t=200 .mu.s, the scheduler has incremented thread B's restart time by, because B is not subject to a time-specific scheduling constraint, (time executed)/(CPU reservation), or 100 .mu.s/50%=200 .mu.s, from 500 .mu.s to 700 .mu.s. FIG. 15E is a data structure diagram showing the scheduling status data structure after the scheduler has moved thread A from the ready list to the processor list and thread B from the processor list to the ready list. FIG. 15F is a data structure diagram showing the scheduling status data structure after the scheduler has executed thread A for 200 .mu.s to increase the restart time of thread A from 500 .mu.s at t=300 .mu.s to 700 .mu.s at t=500 .mu.s. At t=500 .mu.s, thread A completes its time-specific scheduling constraint. FIG. 15G is a data structure diagram showing the scheduling status data structure after the scheduler has removed the urgency data structure corresponding to thread A's only time-specific scheduling constraint and adjusted the remaining urgency data structure accordingly. FIG. 15G shows that the scheduler incremented the restart time of the bottom urgency data structure of thread A's individual urgency stack by (time executed)/(CPU reservation), or 400 .mu.s/30%=1333 .mu.s, from 700 .mu.s to 2033 .mu.s. FIG. 15H is a data structure diagram showing the scheduling status data structure after the scheduler has moved thread B from the ready list to the processor list and thread A from the processor list to the ready list. FIG. 15I is a data structure diagram showing the scheduling status data structure after the scheduler has executed thread B for 666 .mu.s to increase the restart time of thread B by 1333 .mu.s (666 .mu.s/50%), from 700 .mu.s at t=500 .mu.s to 2033 .mu.s at t=1166 .mu.s. FIG. 15J is a data structure diagram showing the scheduling status data structure after the scheduler has moved thread A from the ready list to the processor list and thread B from the processor list to the ready list. FIG. 15K is a data structure diagram showing the scheduling status data structure after the scheduler has executed thread A for the minimum execution period, 100 .mu.s. FIG. 15K shows that the scheduler has incremented thread A's restart time by 333 .mu.s (100 .mu.s/30%), from 2036 .mu.s at t=1166 .mu.s to 2336 .mu.s at t=1266 .mu.s. FIG. 15L is a data structure diagram showing the scheduling status data structure after the scheduler has moved thread B from the ready list to the processor list and thread A from the processor list to the ready list. FIG. 15M is a data structure diagram showing the scheduling status data structure after the scheduler has executed thread B for 167 .mu.s to increase the restart time of thread B by 333 .mu.s (167 .mu.s/50%), from 2033 .mu.s at t=1266 .mu.s to 2366 .mu.s at t=1433 .mu.s. When reviewing the performance of the facility during the scheduling example shown in FIGS. 15A-6M, FIG. 17 shows that, though thread B has a higher CPU reservation (50%) than thread A (30%), thread A received more processor time (400 .mu.s) than thread B (100 .mu.s) during the period during which thread A was subject to a time-specific scheduling constraint (t=0 to 500 .mu.s). When thread A's time-specific scheduling constraint ended at t=500 .mu.s, thread B quickly received exclusive use of the processor until its total processor time bore approximately the same relationship to thread A's total processor time (766 .mu.s: 400 .mu.s) borne by thread A's CPU reservation to thread B's CPU reservation (50%: 30%) at t=1166 .mu.s. After this point, threads A and B continued to receive processor time at a rate approximately proportional to their respective CPU reservations. As such, the scheduler succeeded at both of the competing goals of (1) allocating the processor time necessary to a thread to meet a time-specific scheduling constraint, and (2) balancing overall processor time to threads in proportion to their CPU reservations. FIGS. 18-20 illustrate inheriting the urgency from an urgent thread that has blocked on a resource. When a blocking thread blocks on a resource owned by an owning thread, the owning thread inherits the urgency of the blocked thread, and the processor time consumed by the owning thread while it owns the resource is charged against the owning thread. FIGS. 18A-18F are data structure diagrams showing the contents of the scheduling status data structure that illustrate the inheritance of urgency from a thread that blocks on a resource to the thread that owns the resource. While all of the threads shown in FIGS. 18A-18F are subject to time-specific scheduling constraints to facilitate this example, the approach employed by the scheduler is effective for any combination of threads subject to and not subject to time-specific scheduling constraints. FIG. 18A is a data structure diagram showing the initial state of the scheduling status data structure. FIG. 18A shows the processor, ready, and blocked lists. In FIG. 18A, no threads are blocked, thread A has the earliest restart time and is therefore on the processor list, and thread C owns a mutual exclusion synchronization mechanism called "mutex 1" which only supports ownership by one thread at a time. Further, no thread has any inherited urgencies. FIG. 18B is a data structure diagram showing the state of the scheduling status data structure after thread A has attempted to acquire mutex 1 and has blocked on it pending thread C's ownership of mutex 1. FIG. 18B shows that thread A has been moved to the blocked list. FIG. 18C is a data structure diagram showing the state of the scheduling status data structure after the scheduler has inherited the urgency of a thread blocked on a resource to the thread that owns the resource. FIG. 18C shows that the scheduler has inherited thread A's urgency (restart time=3002 .mu.s, not critical) to thread C. FIG. 18D is a data structure diagram showing the state of the scheduling status data structure after the scheduler has moved the thread with inherited urgency to the processor list to execute. FIG. 18D shows that the scheduler has moved thread C, which has the earliest effective restart time after inheriting thread A's urgency, to the processor list. FIG. 18E is a data structure diagram showing the state of the scheduling status data structure after the thread with inherited urgency has released the resource. FIG. 18E shows that thread A has released mutex 1. In response, the scheduler has revoked thread C's inheritance of thread A's urgency. FIG. 18E further shows that the scheduler has increased the restart time in thread C's top urgency data structure 931 to account for work done by thread C while it was inheriting the urgency of thread A. FIG. 18F is a data structure diagram showing the state of the scheduling status data structure after the scheduler has removed the thread whose inherited urgency was revoked by the scheduler. FIG. 18F shows that the scheduler has replaced thread C, whose restart time is once again the latest, on the processor list with thread C, whose restart time is the earliest. FIG. 19 is a flow diagram showing the steps preferably performed by the scheduler when an executing thread blocks on a resource. In step 1901, the scheduler stores an indication of the resource blocked on in the thread data structure of the blocked thread. In step 1902, the scheduler moves the blocked thread from the processor list to the blocked list. In step 1903, the scheduler inherits the top urgency data structure on the individual urgency stack of the blocked thread to any threads owning the resource blocked on, as well as any threads upon which the owning threads are dependent, either directly or indirectly. Step 1903 involves adding to the inherited urgency component of the thread data structures for the owning threads and any threads upon which the owning threads are dependent. A reference to the cached effective urgency of the blocked thread step 1903 further involves reevaluating the cached effective urgency for each such thread in light of this new inherited urgency. In step 1904, the scheduler performs the steps shown in FIG. 16 to update the scheduling status data structure. After step 1904, there are no more steps that the scheduler must perform for an executing thread blocked on a resource. FIG. 20 is a flow diagram showing the steps preferably performed by the scheduler when an executing thread releases ownership of a resource. In step 2001, the scheduler removes ownership of the resource from the releasing thread by removing the indication stored in its thread data structure that it owns the resource. In step 2002, if one or more other threads are blocked on the resource, then the scheduler continues at step 2003, else the steps shown in FIG. 20 conclude. In step 2003, the scheduler removes from the releasing thread the urgencies that the thread has inherited based on its ownership of the resource. Step 2003 involves removing references to the effective urgency of this thread from the inherited urgency component of the thread data structures for any threads that have inherited the urgency of this thread as a result of this thread being blocked on the resource. Step 2003 further involves reevaluating the cached effective urgency of such threads in light of the removed inherited urgency. In step 2004, the scheduler reassigns ownership of the resource to one of the threads that is blocked on it. The scheduler preferably employs conventional techniques for reassigning synchronization mechanisms in step 2004, but may also consider the relative urgencies of the blocked threads, as well as whether any of the blocked threads are also blocked on other resources. In step 2005, the scheduler moves the thread to which ownership was reassigned from the blocked list to the ready list. In step 2006, the scheduler performs the steps shown in FIG. 16 to update the scheduling status data structure. The steps shown in FIG. 20 then conclude. FIGS. 21-22 illustrate scheduled interrupt handling. FIG. 21 is a flow diagram showing the steps preferably performed by an interrupt service routine adapted for use with the scheduler. These steps are preferably performed synchronously in response to the occurrence of a specific interrupt. The interrupt generally occurs subsequent to a request for input/output services made by a requesting thread. The requesting thread generally blocks on a condition after making the input/output request that is signaled when the input/output request is completed. In step 2101, the interrupt service routine creates a new record in a queue that contains one record for each occurrence of the interrupt not yet processed. The created record contains information conveyed by the interrupt needed to process the interrupt. The created record preferably also contains an indication of the condition blocked on by the requesting thread, which the dedicated interrupt processing thread can signal to return the requesting thread from the blocked list to the ready list when the dedicated interrupt processing thread finishes processing the interrupt. In step 2102, the scheduler signals a condition blocked on by a dedicated interrupt processing thread, discussed below in conjunction with FIG. 22. This allows the blocked dedicated interrupt processing thread to move from the blocked list to the ready list to process the occurrence of the interrupt in response to which the interrupt service routine was executed, as well as any other occurrences of the interrupt represented by other records in the queue. As is discussed in more detail below, some interrupts require little or no substantive processing, and do not require a dedicated interrupt processing thread. FIG. 22 is a flow diagram showing the steps preferably performed by a dedicated interrupt service thread. These steps are preferably performed continuously. In step 2201, the dedicated interrupt service thread blocks on a condition signaled by the interrupt service routine for the interrupts to be processed. In step 2202, when this condition is signaled by the interrupt processing routine in step 2202, if the interrupt queue is empty, then the dedicated interrupt service thread continues at step 2201 to again block on the condition, else the dedicated interrupt service thread continues at step 2203. In step 2203, the dedicated interrupt service thread removes a record from the interrupt queue. In step 2204, the dedicated interrupt service thread processes the interrupt corresponding to removed record using the contents of the remove records. In step 2205, the dedicated interrupt service thread signals the condition blocked on by the requesting thread to which the interrupt processed in step 2204 corresponds. Signaling the condition enables the requesting thread to move from blocked list the to the ready list and become eligible to resume execution. After step 2205, the dedicated interrupt service thread continues at step 2202. As is noted above in conjunction with FIG. 21, a dedicated interrupt service routine such as the one shown in FIG. 22 is only necessary for interrupts whose handling requires a significant amount of processing. For interrupts whose handling does not require a significant amount of processing, no dedicated interrupt service routine is necessary, and the thread waiting for interrupt handling to complete can block on the condition signaled by the interrupt service routine instead of a condition signaled by a dedicated interrupt service thread. FIGS. 23-25 illustrate inheriting urgency from a client thread to a server thread executing on its behalf. When a server thread performs a service on behalf of a client thread, the server thread inherits the urgency of the client thread, and the processor time consumed by the server thread in performing the service is charged against the client thread. FIG. 23 is a flow diagram showing the steps preferably performed by the server and client threads. The diagram shows steps 2311-2313 performed by the client thread and steps 2331-2332 performed by the server thread. After performing zero or more preceding steps (not shown), the client thread continues at step 2311. In step 2311, the client thread calls the server thread, which is preferably created in response to the call. In step 2312, the client thread blocks on a condition that will be signaled by the server thread when the call of step 2311 is completed. In step 2331, the server thread performs the service requested by calling the server thread. In step 2332, the server thread signals the condition blocked on by the client thread in step 2312. The server thread then concludes. In step 2313, the client thread releases the condition in blocked on, if necessary. The client thread then performs zero or more succeeding steps (not shown). The scheduler also preferably enables a server thread on a server computer system to inherit the urgency of a client thread on a separate client computer system. FIG. 24 is a high-level block diagram showing a computer network in which the scheduler preferably performs scheduling across two individual computer systems. The computer network 2499 connects two computer systems 2400 and 2450, both substantially similar to computer system 100 shown in FIG. 19, via network connections 2425 and 2475. The memory 2430 of computer system 2400 preferably contains an operating system 2431, which itself contains a copy of the scheduler 2432, as well as a thread status data structure 2433. Similarly, the memory 2480 of computer system 2450 preferably contains an operating system 2481, which itself contains a copy of the scheduler 2482, as well as a thread status data structure 2483. The memory 2430 of computer system 2400 preferably further contains a program 2436 containing the client thread, while the memory 2480 of computer system 2450 preferably further contains a program 2486 containing the server thread. Given this arrangement of the program 2436 containing the client thread and the program 2486 containing the server thread, computer system 2400 is called the client computer system and computer system 2450 the server computer system. Each computer system, however, is preferably capable of serving as either the client computer system or the server computer system. FIG. 25 is a flow diagram showing the steps preferably performed by the scheduler in the server and client threads on separate computer systems. In step 2531, the server thread, executing in the server computer system, waits for a request to execute. In step 2511, the client thread, executing in the client computer system, sends a request to execute the server thread 2520 to the server computer system. The request 2520 contains the top urgency data structure of the client thread 2521, provided by the scheduler of the client computer system. In step 2532, when the request is received in the server computer system, the server thread pushes the received client thread urgency data structure onto its own individual urgency stack, so as inherit the urgency of the client thread. If the urgency of the client thread subsequently changes, the scheduler for the client computer system preferably sends an additional message to the scheduler for the server computer system containing the updated urgency. In response, the scheduler for the server computer system inherits the updated urgency to the server thread (not shown). When the server thread completes the request, in step 2533, it pops the client thread's urgency data structure, updated to reflect the work done by the server thread on the client thread's behalf, off of its individual urgency stack and sends it in a completion notice 2540 to the client computer system. After step 2533, the server thread continues at step 2531 to wait for the next request to execute the server thread. The completion notice 2540 sent in step 2533 contains the top urgency data structure of the client thread 2541 updated to reflect the work done by the server thread on the client's behalf. In step 2513, when the completion notice is received in the client computer system, the client thread replaces its top urgency data structure with updated urgency data structure 2541, thereby charging itself for the work done on its behalf by the server thread. The steps shown in FIG. 25 then conclude. While the present invention has been described with reference to a preferred embodiment thereof, those skilled in the art will appreciate that various changes in form and detail may be made without departing from the intended scope of the present invention as defined in the appended claims. For example, the present invention need not be practiced in an object-oriented environment. In addition, substantially more sophisticated resource management policies can be implemented, such as policies that do not rely on a strict importance ordering among activities. Further, the scheduler may be readily adapted to schedule the use of other resources, such as hardware devices. The scheduler could also use various other indications of urgency besides the combination of restart times and criticality.
|
Same subclass | ||||||||||
