METHOD AND APPARATUS FOR STRONG AFFINITY MULTIPROCESSOR SCHEDULING

Abstract: A method and apparatus for scheduling threads on a multiprocessor utilize an unlocked local queue for each processor in the multiprocessor and a lockable global dispatch queue accessible by all processors. Threads are selected for movement from the unlocked local queue to the global dispatch queue only when the unlocked local queue contains too many threads that are waiting for a processor. Threads are selected to run on an available processor only after repeated checks to make certain no threads in the processor's unlocked local queue should be run first. As a result, threads assigned to a processor tend to stay with that processor unless the system load is severely unbalanced, thereby improving system performance by increasing cache hits and decreasing lock assertions.

Description: TITLE METHOD AND APPARATUS FOR STRONG AFFINITY MULTIPROCESSOR SCHEDULING INVENTOR JEFFREY V. MERKEY COPYRIGHT NOTICE A portion of the disclosure of this patent document contains material which is subject to copyright protection. The copyright owner has no objection to the facsimile reproduction by anyone of the patent document or the patent disclosure, as it appears in the Patent and Trademark Office patent file or records, but otherwise reserves all copyright rights whatsoever. The copyright owner does not hereby waive any of its rights to have this patent document maintained in secrecy, including without limitation its rights pursuant to 37 C.F.R. § 1.14. FIELD OF THE INVENTION The present invention relates to the allocation and scheduling of processors in a multiprocessing computer system, and more particularly to a thread-scheduling invention which creates a strong affinity between each thread and the processor which is initially allocated to the thread. TECHNICAL BACKGROUND OF THE INVENTION Hardware Many computing systems contain a single central processing unit ("CPU" or "processor") , a primary storage unit directly accessible to the processor, and a secondary storage unit for long-term bulk storage of information. The primary storage typically includes random access memory ("RAM") and the secondary storage typically includes a magnetic disk, optical disk, or similar device. To create more powerful computing systems, these individual architectural components—processors, memories, and disks—have been and are being combined and connected in various ways. A major goal of these alternative architectures is to support parallel processing, that is, processing performed by several processors which are working on different pieces of a given problem at the same time. A parallel processing system is said to be "scalable" if adding additional processors clearly improves the system's performance. Some parallel processing architectures are generally termed "multiprocessors" or "multiprocessing systems." Multiprocessors contain at least two processors which communicate with one another through a "shared memory." Shared memory makes a single virtual address space available to multiple processors, allowing each processor to read and write values at address locations that are accessible to all processors and addressed identically by each processor. Each processor in a multiprocessing system may also have a local private memory, known as its "cache," which is not shared with the other processors. Multiprocessors may be connected to each other and/or to single processor systems in a local area network, wide area network, on the Internet, or by other means. Processors which communicate with each other but do not have a common shared memory form a "multicomputing system." Thus, a local area network is one of many possible types of multicomputing systems. Multiprocessing systems and multicomputing systems are known collectively as "distributed systems." Multiprocessors may be "bus-based" or "switched." One bus-based multiprocessor is illustrated in Figure 1. The multiprocessor, which is indicated generally at 10, includes four processors 12, each of which has its own cache 14. The caches communicate through signal lines 15 using MESI or another familiar protocol. The processors 12 communicate with one another through a shared memory unit 16 which is on a common bus 17 with the processors 12. The shared memory unit 16 typically includes a memory bus controller and RAM. The bus 17 also provides communication between the processors 12 and/or shared memory 16, on the one hand, and a drive 18 capable of reading a medium 19, on the other hand. Typical drives 18 include floppy drives, tape drives, and optical drives. Typical media 19 include magnetic and optical computer-readable media. To read the value of a word from the memory 16, a particular processor such as CPU 1 puts the memory address of the desired word onto the bus 17 and signals that a read is desired. In response, the memory 16 places the value of the addressed word onto the bus 17, thereby allowing the processor CPU 1 to read the value. Writes to the shared memory 16 are performed in a similar way. Unfortunately, if shared memory 16 reads and writes are performed only by using this simple approach, then performance of the multiprocessor 10 drops dramatically as additional processors 12 are added. When too many processors 12 are present, the bus 17 cannot transfer information between the processors 12 and the shared memory 16 as rapidly as requested by the processors 12. System performance then drops because some of the system's processors 12 are idle while they wait for access to the shared memory 16. To reduce the load on the bus 17, copies of the values read or written by a given processor such as CPU 1 may be kept in that processor's cache 14. Each value is stored in the cache 14 with some indication of the address at which that value is kept in the shared memory 16. Addresses corresponding to values stored in the cache 14 are called "cached addresses," while the values stored in the cache 14 are called "cached values." If the address specified in a read request is a cached address, the corresponding cached value is read from the cache 14 and no request is placed on the bus 17. Although caching may dramatically reduce the load on the bus 17, it also introduces potential inconsistencies. Imagine that processors CPU 1 and CPU 2 each read the word at address A0 from the shared memory 16 and that the value read is zero. Then the cache of CPU 1 and the cache of CPU 2 will each indicate that the value stored at address A0 is zero. Suppose CPU 1 then writes the value one to address A0 of the shared memory 16. Then the cache of CPU 1 and the shared memory 16 will each indicate that the value stored at address A0 is one, while the cache of CPU 2 will still indicate that the value stored at A0 is zero. Using one or both of two approaches, known as "write- through caches" and "snooping caches," will prevent such inconsistencies on bus-based multiprocessing systems unless the number of processors is too large. If the number of processors grows too large, alternative architectures may be used. One alternative multiprocessing architecture, known as a "crossbar switch," is indicated generally at 20 in Figure 2. A shared memory is divided into modules 22 which are connectable to processors 24 by signal lines 26. The signal lines 26 may be connected as needed by actuating appropriate crosspoint switches 28. Another alternative multiprocessing architecture, known as an "omega switching network," is indicated generally at 30 in Figure 3. Shared-memory is again divided into modules 32 which are connectable to processors 34 by signal lines 36. The signal lines 36 may be connected as needed by actuating appropriate 2x2 switches 38. In either the crossbar switch multiprocessor 20 (Figure 2) or the omega multiprocessor 30 (Figure 3), some or all of the processors 24, 34 may have a cache similar to the caches 14 in the bus-based multiprocessor 10 (Figure 1). The multiprocessors 20, 30 may also include a drive such as the drive 18 (Figure 1) for reading computer- readable media such as the medium 19. Software Generally Although its underlying hardware limits the theoretical performance of a multiprocessor, in practice limitations imposed by an "operating system" are more frequently encountered. The operating system is software which (among other duties) controls access to the processors. The presence of multiple processors that are capable in theory of working in parallel on a given computational problem does not, in and of itself, make parallel processing a practical reality. The problem must be broken into appropriate parts, the parts must then be efficiently distributed among the processors, and the results of the separate computations must finally be combined to provide a solution to the problem. Computational problems may be divided into "tasks," each of which in turn contains one or more "threads." Each task has its own address space; the address spaces of separate tasks are typically disjoint. Tasks often have other components as well, such as global variables, associated files, communication ports, semaphores, and accounting information. Each thread has some executable code and a set of register values. The register values include a program counter value that indicates which point the thread has reached in its progress through the executable code. Threads may also have associated state information such as a function call stack. A variety of approaches have been tried for allocating processors to tasks and threads. When the processing require- ents of a problem are precisely known before computation to solve the problem is performed, deterministic approaches such as certain graph-theoretic algorithms can be used to efficiently allocate processors to threads or tasks which will collectively solve the problem. However, in most cases the information needed by deterministic approaches is not available until after the computations have finished. Because deterministic approaches are rarely practical, a variety of non-deterministic "heuristic" approaches are used to allocate processors to threads and/or tasks. One centralized approach tries to allocate processors fairly among all waiting users. Under this approach, a user who is not currently using any processors but has been waiting a long time for a processor will always be given the next available processor. The usage information needed to allocate processors fairly is maintained in one central location. To increase the fairness of processor allocation, this approach sometimes stops a thread or task before it has finished using a given processor, saves appropriate state information, and then gives that processor to a different thread or task. Under many allocation schemes, a given processor may be allocated to a group of threads rather than being dedicated to an individual thread. In such cases, steps must be taken to schedule the use of that processor by the individual threads in the group, since only one thread can run at a time on any particular processor. Deterministic scheduling approaches exist which theoretically optimize efficiency, but which are not practical because they require more information than is typically available. Mach Software One heuristic approach to processor scheduling in a multiprocessor system is embodied in the Mach operating system presently under development at Carnegie-Mellon University and elsewhere. Each processor is assigned to exactly one "processor set." Processor sets are then allocated to threads. Each processor set therefore has a set of threads to execute, and steps must be taken to schedule use of the processors by the threads. Goals of Mach scheduling include assigning processor cycles to threads in a fair and efficient way while nonetheless recognizing different thread priorities. Each thread has a priority level ranging from 0 (highest priority) to 31 (lowest priority) . Each processor set has an associated array of global run queues. Figure 4 illustrates an array 40 of global run queues 42 for a processor set PI. Each run queue 42 contains zero or more threads 44 waiting to use a processor in the processor set. Mach defines similar arrays for each of the other processor sets. Each global run queue 42 corresponds to a different priority level. When a thread at a given priority is ready to run, it is placed at the end of the corresponding run queue. Threads which are not ready to run are not present on any of the run queues 42. In the example shown, a priority-three run queue 46 contains two priority-three threads 48 that are ready to run, and a priority-eight run queue 50 contains two priority-eight threads 52 which are ready to run. Two other run queues 42 also contain at least one thread 44; the remaining run queues 42 are presently empty. Each Mach array 40 has three associated variables: an array mutex, a thread count, and a hint. The array mutex (derived from "mutual exclusion") is used to lock the array 40 so that only one processor can access the run queues 42 at a time. The thread count holds the total number of threads 44 currently in the run queues 42 of the array 40. The hint holds a priority level indicating where a Mach scheduler should start looking for the highest priority thread. The highest priority thread will be located either in the run queue for the hint priority level or in a lower priority run queue. The global run queues 42 may be used by each of the one or more processors in the processor set. In addition, each individual processor Pn has its own local run queues. The local run queues are similarly arranged in priority levels from zero through thirty-one. Each local run queue for processor Pn holds "bound" threads, namely, threads that are permanently bound to processor Pn. Typical bound threads include device drivers for I/O devices that are physically accessible only to processor Pn. Bound threads are never placed in one of the global run queues 42. Mach utilizes the run queues 42 and other structures to perform processor" scheduling as follows. Each thread 44 is allocated a maximum quantum of time during which it can have continual use of a processor. When a thread 44 blocks or exits voluntarily, or is preempted because it has run continually for one quantum, the scheduler searches certain run queues to locate the next thread 44 that will be given the processor. If a thread 44 is found at any time during this search, the processor is allocated to that thread 44 and the search ends. The Mach scheduler looks first in the processor's local run queues. If any threads 44 are found, the first thread 44 in the highest priority local run queue is given the processor. The check for threads 44 in the local run queues begins by checking to see whether the local thread count is zero. If it is, the local run queues are all empty. Otherwise, the scheduler uses the local hint value to find the first thread 44 in whichever non-empty local run queue has the highest priority. If all of the local run queues are empty, then the same steps are repeated to search the global run queues 42 for the processor set that contains the processor. If there are no threads 44 in either the local run queues or the global run queues, and if a non-scheduler thread was not preempted to perform the search, then the scheduler repeats the search, possibly after waiting for some predefined period of time. If a ready-to-run thread 44 is located, that thread 44 is allowed to run for at most one time quantum. Then it is stopped and the whole search process is repeated. Mach regularly decreases the priority of the currently running thread 44. Thus, the longer a thread 44 runs, the less successful it is likely to be when competing with other threads 44 for a processor. However, some threads 44 have a limited ability to temporarily increase their own priority, after which their original (lower) priority is restored. A thread 44 may also name another thread 44 as its successor. If a successor thread 44 is named, the local and global run queues are not searched. Instead, the processor is simply given to the successor for at most one quantum of time. Mach's approach to scheduling has two major drawbacks. First, Mach continually preempts threads which are doing useful work, sets them to one side, and then spends valuable processor time performing the searches just described. From a user's perspective, the time spent searching is undesirable administrative overhead that decreases the overall performance of the multicomputing system. The processor made to do the search is prevented from working on the user's problem during the search. Moreover, the scheduler must lock the global run queues 42 while the search is performed. If other processors in the same processor set try to access the locked global run queues 42, they must wait until the first processor finishes. Thus, the search may reduce the efficiency of several processors even though it seeks a thread to run on just one processor. The second drawback to Mach's approach is even more destructive of multiprocessor efficiency. Under Mach, threads 44 tend to migrate from one processor to another processor over time. Bound threads (those in local run queues) only run on a particular processor, but load-balancing concerns traditionally limit such bound threads 44 to device drivers and other threads 44 that simply will not run on other processors. Most threads 44 are not bound, but are allowed to run on any available processor in the processor set. Unfortunately, moving threads 44 between processors may severely degrade system performance because it undercuts the performance gains that would otherwise arise from processor cache usage. With reference to Figures 1 and 4, those of skill in the art will appreciate that running a thread 44 on a given processor 12 tends to fill that processor's cache 14 with the data needed by the thread 44. Over time, the thread 44 therefore tends to receive data from the cache 14 rather than the shared memory 16. As discussed, the cache 14 thereby improves performance of the system 10 by reducing the load on the bus 17. Similar performance gains arise when local processor caches are used in other multicomputing systems, including the systems 20 and 30 shown in Figures 2 and 3, respectively. Moving a thread 44 to a new processor forces the thread 44 to reacquire needed data from the shared memory 16, 22, 32. The data must be reloaded into the processor's cache before the benefits of caching become available again. Indeed, the processor not only acts as though it had no cache during this reloading process, but actually performs worse than similar cache-less processors because of the need to reload the cache. Thus, it would be an advancement in the art to provide a method and apparatus for thread scheduling which reduces the movement of threads between processors in a multiprocessor. It would also be an advancement to provide such a method and apparatus which reduces the time during which processors in a multiprocessor are unable to work because thread scheduling is underway. Such a method and apparatus for multiprocessor scheduling are disclosed and claimed herein. BRIEF SUMMARY OF THE INVENTION The present invention provides a method and apparatus for scheduling the execution of a plurality of threads on a plurality of processors in a multiprocessor computer system. One method of the present invention includes associating an unlocked local queue of threads with each of the processors and maintaining a global dispatch queue of threads which are not presently associated with any processor. The unlocked local queue is accessed only by the processor in question and therefore requires no corresponding mutex or other semaphore to maintain its data integrity. Thus, the number of locks asserted by the multiprocessor's operating system under the present invention is significantly less than under different approaches, and provides the multiprocessor with a corresponding performance increase. The method of the present invention also includes selecting movable threads from the unlocked local queues according to predetermined criteria which tend to restrict the mobility of threads. A thread is moved from its unlocked local queue to the global dispatch queue only if different processors are facing very disparate loads. This creates a strong affinity between processors and threads, which in turn provides the multiprocessor with a performance boost by increasing processor cache usage and decreasing shared memory accesses. In one embodiment of the present invention, the global dispatch queue is a lockable queue to prevent it from being changed by more than one thread at a time. Moving a selected thread is accomplished by locking the global dispatch queue, by then deleting the selected thread from its unlocked local queue and inserting it in the global dispatch queue, and finally unlocking the global dispatch queue. Locking and unlocking involve obtaining and releasing, respectively, a mutex variable that is associated with the global dispatch queue. The selection of movable threads includes identifying a busiest processor among the plurality of processors. Movable threads are selected only from eligible-to-run threads in the unlocked local queue of the busiest processor. One embodiment identifies the busiest processor as that processor which has received the smallest number of sleep requests of any of the processors during a sampling period. Another embodiment identifies the busiest "popular" processor among the plurality of processors. A processor is "popular" when its unlocked local queue contains at least two threads which are eligible to run. The movable threads are then selected only from the eligible threads in the unlocked local queue of the busiest popular processor. Selection of a thread to which control of an available processor will be yielded is accomplished by searching for a suitable thread until one is found and then switching the processor's context to the new thread. One method of the present invention searches, in a predetermined order, at least a portion of the union of the global dispatch queue and the unlocked local queue of the processor to locate at least one eligible thread. Control of the processor is given to an eligible thread found during the search. One embodiment requires that control be yielded to at least one thread that was not found in the global dispatch queue between each pair of instances in which control is yielded to threads found in the global dispatch queue. According to the present invention, one approach to searching includes checking the global dispatch queue for an eligible thread. If no eligible thread is found in the global dispatch queue, the searching step checks the unlocked local queue of the processor. A second approach to searching may be used in embodiments of the present invention which associate a lockable local queue of threads with each of the processors. The lockable local queue is used rather than the unlocked local queue when other processors need to bind a thread, such as a device driver, to the given processor. The unlocked local queues are still present; use of the lockable local queues is typically rare. This alternative approach to searching includes checking the lockable local queue of the processor for an eligible thread, checking the global dispatch queue if no eligible thread is found in the lockable local queue, and then checking the unlocked local queue if no eligible thread is found in the global dispatch queue. Under either approach, searching may also include deter¬ mining whether checking the global dispatch queue will exceed a predetermined relative frequency for global dispatch queue accesses. The global dispatch queue is checked only if checking will not exceed the predetermined relative frequency for global dispatch queue accesses and, under the second approach, if no eligible thread is found in the lockable local queue. The present invention also covers devices and articles for scheduling threads. The features and advantages of the present invention will become more fully apparent through the following description and appended claims taken in conjunction with the accompanying drawings. BRIEF DESCRIPTION OF THE DRAWINGS To illustrate the manner in which the advantages and features of the invention are obtained, a more particular description of the invention summarized above will be rendered by reference to the appended drawings. Understanding that these drawings only provide selected embodiments of the invention and are not therefore to be considered limiting of its scope, the invention will be described and explained with additional specificity and detail through the use of the accompanying drawings in which: Figure 1 is a diagram illustrating a bus-based multi¬ processor architecture. Figure 2 is a diagram illustrating a crossbar switch multiprocessor architecture. Figure 3 is a diagram illustrating an omega switching net¬ work multiprocessor architecture. Figure 4 is a diagram illustrating an array of run queues utilized by the Mach operating system. Figure 5 is a diagram illustrating thread queues according to one embodiment of the present invention. Figure 6 is a diagram illustrating thread queues according to an alternative embodiment of the present invention. Figure 7 is a diagram illustrating a processor thread queue control structure according to the present invention. Figure 8 is a diagram illustrating a global thread queue control structure according to the present invention. Figure 9 is a flowchart illustrating thread scheduling steps according to the present invention. Figure 10 is a flowchart illustrating additional thread scheduling steps according to the present invention. Figure 11 is a flowchart further illustrating a "reschedule threads" step shown in Figure 10. DETAILED DESCRIPTION OF THE PREFERRED EMBODIMENTS Hardware The present invention relates to a method and apparatus for scheduling the use of processors in a multiprocessor computing system by a plurality of threads. Suitable multipro¬ cessors include, without limitation, the bus-based multiprocessor 10, the crossbar switch multiprocessor 20, and the omega switching network multiprocessor 30 illustrated in Figures 1-3. To aid understanding of the invention through specific examples of the hardware used, Figures 1-3 show four processors 12, 24, 34 in each of the multiprocessors 10, 20, 30, and the present disclosure assumes at certain points that the multi¬ processor being used has 32 processors. However, those of skill in the art will appreciate that the present invention is useful in connection with a wide variety of multiprocessors having two or more processors. In addition, although it is preferred that each processor have its own cache, the present invention is also useful in connection with multiprocessors in which some or all of the processors lack a cache. Scheduling Structures Figure 5 illustrates a set of scheduling structures, indicated generally at 60, which includes several queues 62 according to the present invention. The processors (hereafter, 12, 24, or 34 in Figures 1-3 unless stated otherwise) of the multiprocessor may be divided into processor sets containing one or more processors each. However, for clarity of illustration the embodiments illustrated in the Figures assume one processor per processor set. Each processor Pn (with n = 1,2,3,4) has a processor thread queue control structure 63. One implementation of the queues 62 and control structures 63 according to the present invention includes the C and assembly code shown in the attached Listing Appendix. The code includes typedefs and corresponding assembly language declarations which define processor sets and related structures and data types, including without limitation processor_t, psm_t, PCBS, ResourceTagStruc- ture, task_t, thread_desc_t, thread_t, and WorkToDoStructure. Those of skill in the art will appreciate that the scope of the present invention is not limited to embodiments specifically illustrated in the source code provided herein. Those of skill will also readily determine the correspondence between C and assembly language versions of a given data structure or function. They will likewise readily determine when a C version, an assembly language version, a macro, or some other implementation of a function or data structure should be used in particular circumstances. Each processor thread queue control structure 63 includes an unlocked local queue 64 which holds threads 66 that are waiting for a processor. The set of scheduling structures 60 also includes a global thread queue control structure 67. The global control structure 67 includes a global dispatch queue 68 that also holds threads 66 which are waiting for a processor. Most of the threads 66 are user-defined or otherwise created on behalf of a particular application program. However, the last' thread 66 on each unlocked local queue 64 is an "idle" thread 70 created by the operating system according to the present invention. As used herein, "application thread" means a thread other than an idle thread. The idle thread 70 for a given queue 64 runs when no other work is available, that is, when a processor would otherwise be idle. Operation of the idle thread 70 is explained in detail hereafter. Figure 6 illustrates an alternative set of scheduling structures, indicated generally at 80, which also includes various queues 62 according to the present invention. Each processor Pn (with n = 1,2,3,4) has a processor thread queue control structure 81 which includes both an unlocked local queue 64 of threads 66 awaiting a processor and a lockable local queue 82 of threads 66. This alternative set of scheduling structures 80 also includes a global dispatch queue 68 containing threads 66 awaiting a processor. The last thread 66 on each unlocked local queue 64 is an idle thread 84 created by the operating system according to the present invention. The idle threads 84, like the idle threads 70 (Figure 5) , run when no other work is available. Operation of the idle thread 84 is explained in detail hereafter. Figure 7 further illustrates the processor thread queue control structure 81 for processor PI; the control structures 81 of the other processors are organized in a similar manner. The control structure 81 includes a load indicator 86 which indicates how heavily the corresponding processor is loaded. That is, the load indicator 86 provides a measure indicating how much of the available processing capacity is being spent running code in application threads 66 (Figure 6) versus how much capacity is spent running the idle thread 84, waiting for I/O to complete, or otherwise supporting the application threads 66. A presently preferred load indicator 86 includes a sleep request count 88 that indicates how often the threads 66 running on the corresponding processor have been suspended in mid-execution. Lower values in the sleep request count 88 indicate busier processors, and higher values indicate idler processors. Those of skill in the art will appreciate that other measures may also be used as load indicators 86, including without"limitation, cycles spent in the idle thread 70, 84. The processor thread queue control structure 81 also includes an eligible thread count 90. The value stored in the eligible thread count 90 is the total number of threads 66 currently in the unlocked local queue 64 which are ready to run. The queue 64 may also contain threads 66 which are not eligible because they are blocked awaiting I/O or some other resource or result. In one embodiment, the idle thread 84 is counted as eligible; in another it is not. In addition, the processor thread queue control structure 81 includes a lockable queue mutex 92. Those of skill in the art are familiar with the general theory and implementation of semaphores, monitors, and similar mechanisms to protect the integrity of data structures by ensuring that critical regions of code which manipulate those data structures do not unintentionally interfere with one another. A mutex is a type of semaphore which is always either locked or unlocked. A "lock" operation attempts to lock the mutex. If the mutex is unlocked, the lock operation succeeds. If two threads try at the same time to lock the same mutex, only one thread succeeds. If a thread tries unsuccessfully to lock a mutex, the thread blocks (sleeps) until the mutex is available and the lock succeeds. An "unlock" operation unlocks the mutex. A "trylock" operation, also known as a "spinlock" operation, also tries to lock the mutex. But trylock/spinlock returns a status code indicating failure, rather than blocking, if the lock attempt fails. One suitable implementation of mutexes according to the present invention includes a C typedef such as the mutex_t typedef shown in the attached Listing Appendix. This implemen¬ tation also includes several C functions implementing operations on mutexes, including without limitation the following functions: mutex_init() , mutex_sleep_alloc() , mutex_destroy() , mutex_destroy() , mutex_sleep_free() , mutex_link() , mutex_unlink() , mutex_lock() , mutex_unlock() , mutex_trylock() , mutex_priority() , mutex__owner() , mutex_examine() , sema_the_thread() , rundown() , thread_context() , psm_context() , SignalAndLinkMutex() , _dis(), _en(), _cli(), _sti(), GetProcessorTable() , atomic_inc() , atomic_dec() , lock_mutex() , CPush() , CPop() , PutMutex() , GetMutex(), spin_lock() , spin_unlock() , and m_try_lock() . Those of skill in the art will readily determine appropriate alternative implementations. In some alternative implementations, significant events such as acquisition and release of a mutex are logged to assist in debugging. Figure 8 further illustrates the global thread queue control structure 67. The control structure 67 includes an access count 94 which holds an integer value indicating the number of recent attempts to access the global queue 68, as described below. The control structure 67 also includes a global queue mutex 96. The mutex 96 may be implemented using substantially the same techniques as those used to implement the lockable queue mutex 92 (Figure 7) . Scheduling Steps Generally With reference to Figures 5 and 6, in broad terms thread scheduling according to the present invention proceeds as follows. When a processor Pn becomes available, a scheduler for that processor searches the queues 62 to locate a thread 66 to run on that processor. The processor is then allocated to that thread 66. If no application threads 66 are ready to run, the search will locate one of the idle threads 70, 84. Other- wise, the first application thread 66 found will get the processor. A processor may become available in any one of several ways. A thread 66 may finish executing or a thread 66 may voluntarily yield control to the operating system or to a named successor thread 66. In embodiments which utilize the present invention in connection with preemptive scheduling, a processor becomes available when a thread 66 is preempted by the operating system. One embodiment of software for accomplishing thread yielding operations includes the implementations of the function thr_yield() shown in the attached Listing Appendix; those of skill in the art will readily determine alternative implementations. In searching the scheduling structures 60 shown in Figure 5, the scheduler of the present invention looks first in the global dispatch queue 68. If a thread 66 is found, it gets the processor and the search ends. Otherwise, the scheduler checks the unlocked local queue 64 of the processor. If any application threads 66 are present, the first such application thread 66 is given the processor and the search ends. Otherwise, the idle thread 70 for the processor gets the processor. Searches through the scheduling structures 80 shown in Figure 6 proceed in substantially the same manner. However, the lockable local queue 82 is searched before the global dispatch queue 68. If no application thread 66 is found, the processor is given to the idle thread 84 for the processor. In searching either set 60, 80 of scheduling structures, the present invention does not face the problems caused by using multiple prioritized queues such as those used in the Mach operating system. Eliminating explicit priority levels makes the present invention considerably cleaner than Mach in both theory and implementation. In addition to steps relating to searching the queues 62, the present invention includes steps relating to the movement of threads 66 from the unlocked local queues 64 to the global dispatch queue 68. Because the scheduler searches the global dispatch queue 68 before the local unlocked queue 64, when a processor becomes available, moving a thread 66 from the local queues 64 to the global queue 68 increases the likelihood that the thread 66 will be allocated a processor. In practice, threads 66 tend to stay on a given processor until the system load becomes very uneven, with some processors being very busy and other being mostly idle. This creates a "strong affinity" between threads 66 and processors which tends to increase cache usage and thereby improve system performance. Scheduling Steps in Detail Figures 9-11 illustrate specific thread scheduling steps according to the present invention. With reference to Figures 5, 6, and 9, the steps of Figure 9 deal with marking threads 66 as candidates for movement from a local unlocked queue 64 to the global dispatch queue 68. Referring next to Figures 5, 6, and 10, the steps of Figure 10 deal both with the movement of a marked thread 66 to the global queue 68 and with the search for a thread 66 to receive use of an available processor. Referring finally to Figures 5, 6, and 11, the steps of Figure 11 further illustrate the search for a thread 66 to run on the available processor. In one embodiment, thread scheduling is accomplished by three separate threads, each of which performs the steps shown in one of the three Figures 9-11. In some alternative embodiments, the steps shown in two of the Figures are performed at different times by one thread and the steps shown in the third Figure are performed by a second thread. In other alternative embodiments, a single thread performs all the steps at different times, in round-robin fashion. With reference to Figure 9, the present invention includes a step 100 which places all processors in all processor sets on a list of candidate processors. The list may be implemented using a linked list, an array, a bitset, or other familiar techniques. A subsequent step 102 removes from the list all processors which have too few eligible threads. Lack of sufficient eligible threads is assessed by comparing the eligible thread count 90 (Figure 7) with a predetermined threshold value. A presently preferred threshold value is configurable in the range from two to 10, with a default value of two, but those of skill in the art will readily determine whether different threshold values produce better system performance in particular circumstances. A querying step 104 then determines whether any processors remain on the list. If no queue 64 has more than the threshold number of threads 66 waiting to run, no processors will remain on the list. Software implementing the present invention then performs a sleeping step 106 until a later clock tick-based interrupt restarts the software so that it may again perform any required steps according to the process of Figure 9 or an equivalent process. With reference to Figures 5-7 and 9, if one or more processors remain on the list, the querying step 104 is followed by a step 108 which identifies the busiest processor remaining on the list. In embodiments using sleep request counts 88 as load indicators 86, the busiest processor is the processor having the lowest value in its sleep request count 88. During a step 110, the first eligible thread 66 in the unlocked local queue 64 of the busiest processor is then marked as "movable." Marking may be accomplished by setting a bitflag or other familiar means. Only movable threads 66 are moved to the global dispatch queue 68, but not every thread marked movable will actually be moved. With reference to Figures 5-10, a step 112 regularly updates status information in the scheduling structures 60, 80, including the total eligible thread count 90 for each processor. Update occur once per "small time quantum." In one presently preferred embodiment, the step 112 is performed by an interrupt service routine (timer ISR) about eighteen times per second, so the small time quantum is about one-eighteenth of one second. Those of skill in the art will readily determine how often the step 112 should be performed in particular circumstances. A step 114 regularly checks for movable threads 66 in the unlocked local queues 64. The step 114 is preferably performed once about every "large time quantum," where the large time quantum is significantly larger than the small time quantum so that the step 114 is performed much less often than the step 112. In one presently preferred embodiment, the large time quantum is about two seconds, and the step 114 is performed by an interrupt service routine about once every two seconds. In one embodiment the step 114 is performed by an idle thread 70, 84 which contains the local scheduler for a given processor and context-switching code. Those of skill in the art will readily determine how often the step 112 should be performed in particular circumstances. The steps 112 and 114 may be performed independently of one another such that the sequence of operation during any particular period of time being step 112 alone, step 114 alone, step 112 followed by step 114, or step 114 followed by step 112. Indeed, except in those instances where one step is expressly conditioned upon another, the steps of the present invention may be performed in isolation from one another or in orders which differ from the examples shown in the Figures. If any movable threads 66 are found during the step 114, one of those movable threads 66 is moved to the global dispatch queue 68 during a step 116. In one embodiment, the most convenient movable thread 66 is moved; in other embodiments, the movable thread 66 that has waited longest or the moveable thread 66 that is waiting for the busiest processor is moved. The thread 66 being moved is added to the global dispatch queue 68 only after the code moving the chosen thread 66 has locked the global dispatch queue mutex 96; the mutex 96 is unlocked after the chosen thread 66 has been moved. No mutex is needed for the unlocked local queues 64 because they are accessed only by the local scheduler for the processor in question. During a step 118 the queues 62 are searched to locate the next eligible thread 66, and the processor is allocated to that thread 66. The step 118 is described in detail below in connection with Figure 11. After the step 118, or the more frequent step 112, software implementing the steps of Figure 10 performs a sleeping step 120 until a later clock tick-based interrupt restarts the software so that it may again perform any required steps according to Figure 10 or an equivalent process. With reference to Figures 5-8, 10, and 11, a step 122 initially clears a "tight loop" flag whose purpose is to limit the frequency of access to the global dispatch queue 68. Clearing the tight loop flag indicates that code implementing the invention should not execute in a tight loop that excludes a check for threads 66 in the global queue 68. During a step 124, the scheduler checks the lockable local queue 82 for a thread 66 to run. If an eligible thread 66 is found, it is removed from the lockable local queue 82 during a step 126 and given control of the processor during a step 128. The thread 66 is removed only after the code removing the chosen thread 66 has locked the lockable queue mutex 92. The mutex 92 is unlocked after the chosen thread 66 has been removed. If no eligible thread 66 is found in the lockable local queue 82, a step 130 determines whether the tight loop flag is set. If the flag is set, access to the global dispatch queue 68 is denied until the scheduler has completed a predetermined number of passes through the tight loop. The number of passes completed is kept in the global dispatch queue access count variable 94. In one presently preferred embodiment, the access count 94 (also known as the "handicap") is initially set to four; other appropriate values are readily determined by those of skill in the art. The access count 94 is decremented during a step 132 each time a pass through the tight loop comprising steps 124-132 is completed. In one preferred alternative embodiment, the access count is per processor and is kept in an expanded version of the structure 81 rather than being per global dispatch queue and being kept in the structure 67. That is, a separate access count is kept for each processor rather than a single access count being kept for all processors. If the access count 94 reaches zero in the tight loop, or if the tight loop flag is not set, then a step 134 checks the global dispatch queue 68 for an eligible thread. If a thread 66 is found, it is removed from the global dispatch queue 68 during a step 136 and given control of the processor during a step 138. The thread 66 is removed only after the code removing the chosen thread 66 has locked the global dispatch queue mutex 96. The mutex 96 is unlocked after the chosen thread 66 has been removed. If no eligible thread is found in the global dispatch queue 68, the unlocked local queue 64 is checked during a step 140. If an eligible thread 66 other than an idle thread 70, 84 is found, that eligible thread 66 is removed from the unlocked local queue 64 during a step 142 and given control of the processor during a step 144. No mutex guards access to the unlocked local queue 64 because that queue 64 is accessed only by code running on the processor in question. If the only eligible thread 66 found in the unlocked local queue 64 is the idle thread 70, 84, then control of the processor goes to or remains with the idle thread 70, 84. A presently preferred embodiment runs the search portions of the scheduler as part of the idle thread 70, 84. The available processor runs the idle thread 70, 84 to find a successor thread. Control of the processor thus remains with the idle thread 70, 84 through a step 146 that sets the tight loop flag and through the subsequent searches described above until another eligible thread 66 is located and given control of the processor. In one embodiment of the present invention, the code running on the idle thread 70, 84 includes the function worker_thread() and/or mk_worker_thread() shown in the attached Listing Appendix. Those of skill in the art will readily determine alternative embodiments. In one embodiment, the idle thread code is initialized by operating system code which includes the functions SMP_START(), SMPMainThread() , SMPAddProcessor() , and engine_init() , and supporting code, shown in the attached Listing Appendix. Those of skill in the art will readily determine alternative approaches to initialization of a multiprocessor system according to the present invention. One embodiment of the thread scheduling steps of the present invention includes the following code for transferring control of the processor and performing related steps: context_switch() , thread_switch() , thread_lock() , thread_unlock() , thread_start() , shutdown() , task_lock() , task_unlock() , get_target_and_switch() , reaper_thread() , pset_remove_task() , pset_remove_thread() , context_halt() , ProcessFastWorkToDos() , and related code. Those of skill in the art will readily determine alternative embodiments according to the present invention. In summary, the present invention provides a method and apparatus for thread scheduling which reduces the movement of threads between processors in a multiprocessor. Threads are moved to a different processor only after being moved from an unlocked local queue into the global dispatch queue and thence to another processor. Threads are marked movable only if they are eligible to run and if they are in an unlocked local queue that has more than a predetermined number of eligible threads. Moreover, even if several threads are marked movable, at most one thread is moved to the global dispatch queue in each large time quantum. These and other features of the invention create a strong affinity between threads and processors so that threads tend to remain on the same processor unless the queues for different processors vary substantially in size from one another. This strong affinity improves system performance significantly on multiprocessor systems which have one or more processor caches. The present invention also reduces the time during which processors in a multiprocessor are unable to work because thread scheduling is underway. Use of lockable thread queues is minimized, so fewer locks are asserted on the system bus. Each processor also preferably has its own scheduler. Those of skill will appreciate that preferred embodiments of the present invention report errors and other conditions which interfere with the invention as it assists users in recovering files. Suitable error reporting and recovery means are readily determined by those of skill in the art. Suitable techniques for diagnosing and debugging implementations of the present invention are likewise readily determined by those of skill in the art. With reference to all Figures, articles of manufacture within the scope of the present invention include a co puter- readable storage medium such as the medium 19 in combination with the specific physical configuration of a substrate of the computer-readable storage medium. The substrate configuration represents data and instructions, including without limitation the data structures and instructions illustrated and discussed in connection with Figures 5-11, which cause the multiprocessors 10, 20, 30 to operate in a specific and predefined manner as described herein. Suitable storage devices include floppy disks, hard disks, tape, CD-ROMs, RAM, and other media readable by a multiprocessor. Each such medium tangibly embodies a program, functions, and/or instructions that are executable by the multiprocessor to perform thread scheduling steps of the present invention substantially as described herein. Although particular apparatus and article embodiments of the present invention are expressly illustrated and described herein, it will be appreciated that additional and alternative apparatus and article embodiments may be formed according to methods of the present invention. Similarly, although particular method steps of the present invention are expressly described, those of skill in the art may readily determine additional and alternative steps in accordance with the apparatus and articles of the present invention. Unless otherwise expressly indicated, the description herein of methods of the present invention therefore extends to corresponding apparatus and articles, and the description of apparatus and articles of the present invention extends likewise to corresponding methods. Section headings herein are for convenience only. The material under a given section heading is not necessarily the only material herein on that topic, nor is it necessarily limited only to material on that topic. The invention may be embodied in other specific forms without departing from its essential characteristics. The described embodiments are to be considered in all respects only as illustrative and not restrictive. Any explanations provided herein of the scientific principles employed in the present invention are illustrative only. The scope of the invention is, therefore, indicated by the appended claims rather than by the foregoing description. All changes which come within the meaning and range of equivalency of the claims are to be embraced within their scope. What is claimed and desired to be secured by patent is: LISTING APPENDIX Copyright 1981-1995 Novell, Inc. typedef struct _MLOCK { struct _MLOCK *mtxflink; struct _MLOCK *mtxblink; LONG *cthread_head; LONG *cthread_tail; LONG cthread_mutex; LONG mutex_lock; LONG mutex_waiters; LONG mutex_maxwaiters; LONG mutex_owner; LONG mutex_maxwait; BYTE *mutexname; LONG mutex_count; struct ResourceTagStructure *rtag; LONG mutex_spl; LONG picrec_t; LONG mutex_lock_count; LONG mutex_lock_fail; LONG mutex_reader_count; LONG mutex_type; LONG thread_owner; LONG mutex_res[62] ; } mutex t; typedef struct _processor_set { thread_desc_t *ps_runq_head; /* runq for this set (head) */ thread_desc_t *ps_runq_tail; /* runq for this set (tail) */ mutex t *ps runq_mutex; /* runq for this set (mutex) */ LONG runq_count; thread_desc_t *idle_queue_head; /* idle processors (head) */ thread_desc_t *idle_queue_tail; /* idle processors (tail) */ LONG idle_count; /* how many ? */ mutex_t *idle_lock; /* lock for above */ PSM *processors_head; /* all processors here (head) */ PSM *processors_tail; /* all processors here (tail) */ mutex_t *processor_mutex; /* all processors here (mutex) */ LONG processor_count; /* how many ? */ LONG empty; /* true if no processors */ task_t *tasks_qhead; /* tasks assigned (head) */ task_t *tasks_qtail; /* tasks assigned (tail) */ mutex_t *tasks_mutex; /* tasks assigned (mutex) */ LONG task count; ■ how many */ thread_desc_t *threads_qhead; /*threads in this set (head)*/ thread_desc_t *threads_qtail; /*threads in this set (tail)*/ SUBS T7UTE SHEET (RULE 26) LONG thread_count; /* how many */ LONG ref_count; /* structure ref count */ mutex_t *ref_lock; /* lock for ref count */ LONG *pset_head; /* link for all_psets */ LONG *pset_tail; /* link for all_psets */ mutex_t *pset_mutex; /* link for all_psets */ LONG active; /* is pset in use */ mutex_t ♦processor t lock; /* lock for everything else */ LONG *pset_self; /* port for operations (not used) */ LONG *pset_name_self; /* information port (not used)*/ LONG max_priority; /* maximum priority */ LONG policies; /* bit vector for policies */ LONG set_quantum; /* current default quantum */ LONG mach_factor; /* mach_factor */ LONG load_average; /* load_average */ LONG sched load; /* load avg for scheduler */ struct ResourceTagStructure *pset rtag; /* Resource Tag */ thread_desc_f *ps_hpq_head; /* runq for this set (head) */ thread_desc_t *ps_hpq_tail; /* runq for this set (tail) */ mutex_t *ps_hpq_mutex; /* runq for this set (mutex) LONG hpq_count; thread_desc_t *ps_mpq_head; /* runq for this set (head) */ thread_desc_t *ps_mpq_tail; /* runq for this set (tail) */ mutex_t *ps_mpq_mutex; /* runq for this set (mutex) LONG mpq_count; thread_desc_t *ps_lpq_head; /* runq for this set (head) */ thread_desc_t *ps_lpq_tail; /* runq for this set (tail) */ mutex_t *ps_lpq_mutex; /* runq for this set (mutex) LONG lpq_count; thread_desc_t *ps_qsq_hea ; /* runq for this set (head) */ thread_desc_t *ps_qsq_tail; /* runq for this set (tail) */ mutex_t *ps_qsq_mutex; /* runq for this set (mutex) */ thread_desc_t *ps_lwq_head; /* runq for this set (head) */ thread_desc_t *ps_lwq_tail; /* runq for this set (tail) */ mutex_t *ps_lwq_mutex; /* runq for this set (mutex) */ LONG lwq_count; struct WorkToDoStructure *ps_fwq_head; /* runq this set (head) */ struct WorkToDoStructure *ps_fwq_tail; /* runq for this set (tail) */ mutex t *ps fwq_mutex; /* runq for this set (mutex) '/ struct WorkToDoStructure *ps_dwq_head; /* runq for this set (head) */ struct WorkToDoStructure *ps_dwq_tail; /* runq for this set (tail) */ mutex_t *ps_dwq_mutex; /* runq for this set (mutex) struct WorkToDoStructure *ps_wtdq_head; /* runq for this set (head) */ -26- SUBSTITUTE SHEET RULE 26} struct WorkToDoStructure *ps_wtdq_tail; /* runq for this set (tail) */ mutex_t *ps_wtdq_mutex; /* runq for this set (mutex) BYTE *ps_name; /* processor set name */ thread_desc_t *ps_hipri_head; thread_desc_t *ps_hipri_tail; mutex_t *ps_hipri_mutex; LONG hipri_count; struct _processor_set *set_flink; struct _processor_set *set_blink; task_t *active_task_head; task_t *active_task_tail; mutex_t *active_task_mutex; LONG rr_interval; } processor_t; typedef struct _PSM { struct _PSM *p_flink; struct _PSM *p_blink; struct _PSM *psm_flink; struct _PSM *psm_blink; struct _PSM *psm_default_flink; struct _PSM *psm_default_blink; LONG ProcessorNu ber; LONG ProcessorState; LONG RunningThread; LONG LocalThread; LONG WorkerThread; LONG CurrentTask; LONG PreemptionDisabledFlag; LONG RegisterWorkSpaceEAX; LONG RegisterWorkSpaceEBX; LONG RegisterWorkSpaceECX; LONG RegisterWorkSpaceEDX; LONG RegisterWorkSpaceESP; LONG util_load; LONG LowPriorityPollCount; LONG LocalEnginePollCount; LONG IdlePollCount; LONG Devicelnterrupts; LONG ProcessorStack; BYTE *StackAllocation; LONG InNetWareFlag; BYTE *WorkToDoStack; LONG WorkToDoStackPtr; LONG Nestedlnterrupts; LONG engine_weight; LONG workload; LONG total_cpu_time; LONG debugger_address_space; LONG current_address_space; LONG debugger_acknowledge; LONG debugger_signal; / / // PSM Switch Routines / / LONG s version ; / / spl -27- *s_spl_spl) (); // spl *s_splx_hard) () ; // spl *s_splx_soft) () ; // spl *s_spl_hard) () ; // spl *s_spl_soft) () ; // spl *s_picreload) () ; // picreload *s_enableint) () ; // enableint *s_disableint) () ; // disableint *s_dev_common) () ; // dev_common *s_apic_common) () // apic_common *s_nenableint) () ; // nenable *s_ndisableint) () // ndisable *s_softint_exit) ( // softint exit *s_devint0) () ; // Clock interrupt *s_clock) () ; // Clock interrupt *s_configure) () ; // psm_configure *s_intr_init) () ; // psm_intr_init *s_intr_start) () ; // psm_intr_start *s_timer_init) () ; // psm_timer_init *s_online_engine) 0; // psm_online_engine *s_offline_engine ) (); // psm_offline_engine *s_offline_self) ( ); // psm_offline_self *s_map_engine) () ; // psm_map_engine *s_send_xintr) () ; // psm_send_xintr *s_send_xintrb) () // psm_send_xintrb *s_usec_time) () ; // psm_usec_time *s_ledctl) () ; // psm_ledctl *s_localid) () ; // psm_localid *s_enarmint) () ; // psm_enarmint *s_disrmint) () ; // psm_dismint *s_eoi) () ; // psm_eoi *s_pde) () ; // psm_pde *s_gdt) (); // psm_gdt *s_idt) (); // psm_idt *s_timer_dis) () ; // psm_timer_dis *s timer en) () ; // ps timer en // // MicroKernel Processor Structure Scheduler Section // LONG *p_runq_head;/* local runq for this processor (head) LONG *p_runq_tail;/* local runq for this processor (tail) LONG *p_runq_mutex; LONG *processor_qhead;/* idle/assign/shutdown queue link LONG *processor_qtail;/* idle/assign/shutdown queue link LONG *processor qmutex;/* idle/assign/shutdown queue link LONG state; /* See below */ LONG *active_head; /* active runq for this processor */ LONG *active_tail; /* active runq for this processor */ LONG active_count; LONG load_quantum; LONG load_average; LONG *processor_set; /* processor set I belong to */ LONG *task head; -28- SUBSTmfTE SHEET RULE26 LONG *task_tail; LONG *processors_head; /* all processors in set (head) */ LONG *processors_tail; /* all processors in set (tail) */ LONG *processors_mutex; /* all processors in set (mutex) '/ LONG *processor_set_default; LONG *psm_mutex; /* mutex for everything else */ LONG p_runq_count; } PSM; typedef PSM psm_t; /define TH_WAIT 0x01 /* thread is queued for waiting */ /define TH_SUSP 0x02 /* thread has been asked to stop */ /define TH_RUN 0x04 /* thread is running or on runq */ /define TH_UNINT 0x08 /* thread is waiting, not interruptable */ /define TH_HALTED * 0x10 /* thread is halted at clean point ? */ /define TH_IDLE 0x80 /* thread is an idle thread */ /define TH_SLEEP 0x20 /* thread is sleeping */ /define TH_EVENT 0x40 /* thread is waiting for an event */ /define TH_SEMA 0x0400 /* thread is waiting for semaphore */ /define TH_BARRIER 0x20000000 /define TH_RWLOCK 0x40000000 /define TH_MUTEX 0x80000000 /define TH_RUNDOWN OxCOOOOOOO /define TH_NWHALT OxOCOOOOOO /define TH_DELAY OxOOCOOOOO /* thread is delayed */ /define TH_LPPOLL 0x0800 /* thread is a poller (lowpri) */ /define TH HPPOLL 0x1000 /* thread is a poller (hipri) */ /define TH_SCHED_STATE (TH_WAITjTH_SUSP|TH_RUNjTH_UNINT) /define TH_SWAPPED 0x0100 /* thread has no kernel stack */ /define TH_SW_COMING_IN 0x0200 /* thread awaits kernel stack */ /define TH_SWAP_STATE (TH_SWAPPED | TH_SW_COMING_IN) /define TH MIGRATE 0x10000000 task is queued for waiting */ task has been asked to stop task is running or on runq */ task waiting uninteruptibly task halted at clean point ? task is an idle thread */ task is sleeping */ task is waiting for an event task waiting for a semaphore typedef struct PCBStructure { struct PCBStructure *Link; struct PCBStructure *SemaphoreLink; /* has to be the same offset as in SemphoreStructure */ -29- SUBSi πε SHEET (βUl£ 26) -30- SUBSmTE SHEET <i_UL£ 26) LONG thread_load_average; LONG thread_policy; LONG thread_call_gate; LONG (*thread_callback) () ; LONG (*thread_cleanup) () ; struct PCBStructure *thread_Cflink; struct PCBStructure *thread_Cblink; LONG *thread_mutex_chain; LONG *thread_desc_task; /* Task to which belong */ struct PCBStructure *thread_list_flink; /* list of threads in task */ struct PCBStructure *thread_list_blink; /* list of threads in task */ LONG *marshall_mutex; /* list of threads in task */ struct PCBStructure *pset_threads_flink; /* list of all threads in proc set*/ struct PCBStructure *pset_threads_blink; /* list of all threads in proc set*/ LONG *pset_threads_mutex; /* list of all threads in proc set*/ LONG *thread_lock; LONG ref_count; /* number of references to me */ LONG *event_chain; /* event we are waiting on */ LONG suspend_count; /* internal use only */ LONG *pcb_processor_set; /* assigned processor set */ LONG *bound_processor; /* bound to processor ?*/ LONG may_assign; /* may assignment change? */ LONG assign active; /* someone waiting for may_assign 7 LONG *last_processor; /* processor this last ran on */ LONG *thread_parms; LONG thread_stacksize; LONG stack_chain; LONG smpflag; LONG cookie; LONG *join_head; LONG *join_tail; LONG thread_status; LONG stack_flag; LONG preempt_flag; LONG halt_flag; LONG PermittedSignalMask; LONG ActiveSignalMask; LONG MigrationStack; LONG MarshaHReturn; LONG MarshallSMP; LONG CreatedlnNetware; Work To Do in PCB */ LONG WTDLink; LONG WTDRoutine; LONG WTDRTag; LONG WTDAmount; LONG WTDWhen; LONG WTDThreadID; LONG WTDStackSp; struct PCBStructure *sync_flink; struct PCBStructure *sync_blink; -31- S-ϋβSTTTUTE SHEET (RULE 26) LONG SleepBit; LONG *thr_TLink; void (*thr_TCallBackProcedure) (LONG thr_parameter) ; LONG thr_TCallBackParameter; LONG thr_TCallBackWaitTime; LONG thr_TResourceTag; LONG thr TWorkWakeUpTime; LONG thr_TSignature; LONG cpu_time; LONG cpu_start_time; LONG cpu_stop_time; LONG semaphore_transition; LONG netware_affinity; LONG migrate_and_bind; LONG target_bind; LONG thread_weight; LONG processor_weight; LONG weight_handle; LONG fault_flag; LONG Spares2[711] ; LONG FirstRPCStack; LONG StackGaurdPage; LONG RecursiveEntry; } PCBS; typedef PCBS thread_desc_t; typedef LONG thread_t; ;Task's lock ;Number of references to me ;Task has not been terminated ;Address space description ;Address space description ;list of tasks assigned to ;list of tasks assigned to ;list of tasks assigned to ;Internal scheduling only ;list of threads (head) ;list of threads (tail) ;list of threads (mutex) ;number of threads ;processor set for new ;can assigned pset be ;waiting for may_assign ;outstanding stops ;for new threads ;for new threads ;total user time for dead ;total system time for dead 0 ;port mutex (not used) 0 ;not a right, doesn't hold ref 0 a send right (not used) 0 a send right (not used) 0 a send right (not used) 0 0 0 0 0 0 0 0 0 0 0 0 task ResourceTagStructure struc RTLink dd • ? RTModule dd RTSignature dd • ? RTResourceCount dd RTCustomData dd 4 dup (?) RTDescriptionString db ResourceTagStructure ends struct WorkToDoStructure { struct WorkToDoStructure *Link; /* not touched by caller. void (*ProcedureToCall) () ; /* always set by caller '/ struct ResourceTagStructure *WorkResourceTag; /* always set by caller */ LONG Po11CountAmount; /* set by caller to ScheduleDelayedWorkToDo; not used by ScheduleWorkToDo " LONG PollCountWhen; /* scratch area for ScheduleDelayedWorkToDo; not used by ScheduleWorkToDo */ LONG Reservedl; LONG Reserved2; }; void _cli(void); /pragma aux _cli = OxFA; // cli public context_halt context_halt proc cli pushfd and dword ptr [esp], NOT 00004000h popfd pushfd or dword ptr [esp], 00000200h ; turn interrupts on push cs push eax -33- SUBSTITUTE SHEET ( UL£ 26) push ecx push edx mov eax, [esp + 12] cs mov ecx, [esp + 16] flags mov e-d ' ' " x-, [esp + 20] return address mov [esp + 2200]],, ecx flags -> mov [esp + 16] , eax cs-> mov [esp + 12] , edx return address-> push ebx push ebp push esi push edi push ds push es push fs call GetProcessorTable mov eax, v_psmtable[eax * 4] mov esi, [eax] .RunningThread mov [esi] .StackSP, esp mov ebx, [esi] .pcb_thread_lock mov [ebx] .mutex_lock, 0 push eax push [eax] .ProcessorNumber call [eax] .s_offline_self add esp, 4 pop eax mov [eax] .ProcessorState, PROCESSOR_OFF_LINE clear the busy task mov eax, [eax] .ProcessorNumber mov eax, TSSGateTable[eax * 4] mov [eax] .TSSType, 10001001b wbinv mov eax, L2CACHE or eax, eax jnz /halt_processor db OFh db 09h /halt_processor: hit context_halt endp public context_switch context_switch proc cli pushfd and dword ptr [esp], NOT 00004000h popfd pushfd or dword ptr [esp], 00000200h ; turn interrupts on push cs push eax push ecx push edx mov eax, [esp + 12] ; cs -34- S-JE-SXTTVTE SHEET (RULE 28} context_switch endp void _dis(void); /pragma aux dis = 0x9C OxFA; // pushf CLI void _en(void) ; /pragma aux _en = 0x9D; // popf LONG engine_init(void) { -35- SUBSTITUTE SHEET (RSJLE 26) thread_desc_t *local_thread, *work_thread; register int i; CPU_Type[0] = GetCPUIDAndType() ; v_mutex = mutex_sleep_alloc(MSG("processor_set queue mutex", 49)); processor_set_create(&v_default, MSG("Netware SMP Default Set", 50)); task_create(NULL, NULL, &v_task_default, OSDomain, MSG("Netware SMP Default Task", 51)); task_assign(v_task_default, v_default, ASSIGN_THREADS) ; task_priority(v_task_default, 100, CHANGE_THREADS) ; task_policy(v_task_default, POLICY_FULL_SMP, CHANGE_THREADS) ; task_assign(v_task_default, v_default, NULL); v_processor[0] .ProcessorNumber = 0; RecalibrateEngineUtilization(v_processor[0] .ProcessorNumber) ; v_processor[0 ' ] .StackAllocation = (BYTE *) Alloc((LONG) 8192, (LONG) StackRTag); CSetB(0x00, (LONG) v_processor[0] .StackAllocation, 8192); if (!v_processor[0] .StackAllocation) { return (LONG) (1) ; } v_processor[0] .ProcessorStack = (LONG) &v_processor[0] .StackAllocation[4096] ; v_processor[0] .WorkToDoStack = (BYTE *) Alloc((LONG) (8192 * 2), (LONG) StackRTag); CSetB(0x00, (LONG) v_processor[0] .WorkToDoStack, 8192 * 2) ; if ( !v_processor[0] .WorkToDoStack) { return (LONG) (1) ; } v_processor[0] .WorkToDoStackPtr = (LONG) &v_processor[0] .WorkToDoStack[8192] ; v_processor[0] .ProcessorState = PROCESSOR_RUNNING; // v_processor[0] .os_address_space = (LONG) OSDomain; v_processor[0] .debugger_address_space = (LONG) OSDebuggerDomain; v_processor[0] .processor_set_default = (LONG *) v_default; v_processor[0] .p_runq_mutex = (LONG *) mutex_sleep_alloc(MSG("psm_t run queue mutex", 52)); v_processor[0] .processor_qmutex = (LONG *) mutex_sleep_alloc(MSG("psm_t idle queue mutex", 53)); v_processor[0] .processors_mutex = (LONG *) mutex_sleep_alloc(MSG("psm_t processor_t mutex", 54)); v_processor[0] .psm_mutex = (LONG *) mutex_sleep_alloc(MSG("psm_t table mutex", 55)); for (i=0; i < MAXIMUM_NUMBER_OF_PROCESSORS; i++) { v_processor[i] .processor_set = (LONG *) v_default; } thread_create(NULL, &local_thread, MSG("netware kernel", 56), 8192, NULL, SMPAddProcessorZero) ; thread_create(NULL, &work_thread, MSG("worker thread", 57), 8192, NULL, mk_worker_thread) ; work_thread->smpflag = TRUE; v_processor[0] .LocalThread = (LONG) local_thread; v_processor[0] .WorkerThread = (LONG) work_thread; v_processor[0] .RunningThread = v_processor[0] .LocalThread; local_thread->bound_processor = (LONG *) &v_processor[0] ; -36- SUBSTITUTE SHEET (RULI 26) NetWareContext = (thread_desc_t *) local_thread; local_thread->thread_policy J= SCHED_FIFO; local_thread->thread_state = TH_RUN; work_thread->thread_state = TH_WAIT; NumberOfRegisteredProcessors++; return (LONG) (0) ; } void _flush_tlb(void) ; /pragma aux _flush_tlb = OxOF 0x20 0xD8 OxOF 0x22 0xD8; mutex_t *GetMutex(void) { mutex_t *mutex = NULL; if (MutexListHead) { spin_lock(MutexListMutex) ; if (MutexListHead) { mutex = MutexListHead; MutexListHead •= mutex->mtxflink; if (MutexListHead) MutexListHead->mtxblink = NULL; else MutexListTail = NULL; mutex->mtxflink = (mutex_t *) NULL; mutex->mtxblink = (mutex_t *) NULL; } spin_unlock(MutexListMutex) ; } if (FreeMutexElements) FreeMutexElements—; return (mutex_t *) mutex; } align 16 public GetProcessorTable GetProcessorTable proc mov eax, cr2 cmp eax, 31 jg /reload_cr2 ret /reload_cr2: call v_processor.s_localid mov cr2, eax ret GetProcessorTable endp thread_desc_t *get_target_and_switch(psm_t *psm, thread_desc_t ♦thread) { processor_t *pset; task_t *task; thread_desc_t *target_thread, *t; if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } cpu_stop(thread, psm); pset = (processor_t *) psm->processor_set; if (psm->IdlePollCount++ > WORKER_THREAD_QUANTUM) { -37- HEET R0U26 psm->IdlePollCount = 0; target_thread = (thread_desc_t *) ps_n->WorkerThread; goto reschedule_exit; } if (psm->processor_qhead) { spin_lock( (mutex_t *) psm->processor_qmutex) ; while (psm->processor_qhead) { target_thread = (thread_desc_t *) psm->processor_qhead; psm->processor_qhead = (LONG *) target_thread->pset_threads_flink; if (psm->processor_qhead) { t = (thread_desc_t *) psm->processor_qhead; t->pset_threads_blink = NULL; } else psm->processor_qtail = NULL; target ' thread->pset threads flink = (thread desc t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; target_thread->assign_active = FALSE; target_thread->thread_state = TH_WAIT; if ( !psm->active_head) { psm->active_head - (LONG *) target_thread; psm->active_tail = (LONG *) target__thread; target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; } else { t = (thread_desc_t *) psm->active_tail; t->pset_threads_flink - target_thread; target_thread->pset_threads_flink = NULL; target_thread->pset_threads_blink = (thread_desc_t *) psm->active_tail; psm->active_tail = (LONG *) target_thread; } } spin_unlock( (mutex_t *) psm->processor_qmutex) ; } if (pset->active_task_head) { spin_lock(pset->active_task_mutex) ; if (pset->active_task_head) { task = pset->active_task_head; pset->active_task_head = (task_t *) task->task_flink; if (pset->active_task_head) pset->active_task_head->task_blink = NULL; else pset->active_task_tail = NULL; task->taεk_flink = (task_t *) NULL; task->task_blink = (task_t *) NULL; } spin_unlock(pset->active_task_mutex) ; _flush_tlb() ; } if (psm->p_runq_count) -38- SUESΠTUIE SHEET (RULE26) psm->p_runq_count—; else { if (pset->ps_runq_head) { pset_runq_lock(pset) ; if (pset->ps_runq_head) { target_thread = pset->ps_runq_head; if ( (target thread->thread state == TH MIGRATE) I I I I (target_thread->thread_state == TH_SEMA) I I I I (target_thread->thread_state == TH_DELAY) ) pset->ps_runq_head = (thread_desc_t *) target_thread->pset_threads_flink; if (pset->ps_runq_head) pset->ps_runq_head->pset_threads_blink = NULL; else pset->ps_runq_tail = NULL; target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; if (lpsm->active_head) { psm->active_head = (LONG *) target_thread; psm->active_tail = (LONG *) target_thread; target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; } else { t = (thread_desc_t *) psm->active_tail; t->pset_threads_flink = target_t.hread; target_thread->pset_threads_flink = NULL; target_thread->pset_threads_blink = (thread_desc_t *) psm->active_tail; psm->active_tail = (LONG *) target_thread; } } else if (target_thread->StackSP) { thread_lock(target_t.hread) ; pset->ps_runq_head = (thread_desc_t *) target_thread->pset_threads_flink; if (pset->ps_runq_head) pset->ps_runq_head->pset_threads_blink = NULL; else pset->ps_runq_tail = NULL; target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; pset_runq_unlock(pset) ; _flush_tlb() ; goto reschedule_exit; } } pset_runq_unlock(pset) ; flush tlb() ; -39- 2 if (psm->active_head) { // DEBUG CODE if (psm->active_tail) { t = (thread_desc_t *) psm->active_tail; if (psm->active_head !■= psm->active_tail) { if (!t->pset_threads_blink) BREAK() ; } } // DEBUG CODE psm->active_count = NULL; target_thread - (thread_desc_t *) psm->active_head; while (target_thread) { if (target_thread->thread_state == TH_WAIT) psm->active_count++; target_thread = target_thread->pset_threads_flink; } target_thread = (thread_desc_t *) psm->active_head; while (target_thread) { if (target_thread->halt_flag == TH_HALTED) { if (psm->active_head == (LONG *) target_t.hread) { psm->active_head = (LONG *) target_thread->pset_threads_flink; if (psm->active_head) { t = (thread_desc_t *) psm->active_head; t->pset_threads_blink = NULL; } else psm->active_tail = NULL; } else { target_thread->pset_threads_blink->pset_threads_flink = target_thread->pset_threads_flink; if (target_thread != (thread_desc_t *) psm->active_tail) target_thread->pset_threads_flink->pset_threads_blink = target_thread->pset_threads_blink; else psm->active_tail = (LONG *) target_thread->pset_threads_blink; } target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; rundown(target_thread) ; target_thread = (thread_desc_t *) psm->active_head; continue; } else if ((target_thread->thread_state != TH_MIGRATE) && (target_thread->thread_state != TH_SEMA) && (target_thread->thread_state != TH_DELAY) && (target_thread->StackSP != 0)) { if (psm->active_head == (LONG *) target_t.hread) { psm->active_head = (LONG *) target_thread->pset_threads_flink; -40- SUBSTITUTE SHEET (RIBI26) if (psm->active_head) { t = (thread_desc_t *) psm->active_head; t->pset_threads_blink = NULL; } else psm->active_tail = NULL; } else { target_thread->pset_threads_blink->pset_threads_flink = target_thread->pset_threads_flink; if (target_thread 1= (thread_desc_t *) psm->active_tail) target_thread->pset_threads_flink->pset_threads_blink = target_thread->pset_threads_blink; else psm->active_tail = (LONG *) target_thread->pset_threads_blink; } target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; target_thread->assign_active = TRUE; // DEBUG CODE if (psm->active_tail) { t = (thread_desc_t *) psm->active_tail; if (psm->active_head != psm->active_tail) { if (!t->pset_threads_blink) BREAK(); } } // DEBUG CODE //*************************** goto reschedule_exit; } target_thread = target_thread->pset_threads_flink; } } target_t.hread = (thread_desc_t *) psm->WorkerThread; reschedule_exit: ; psm->load_quantum++; target_thread->thread_state = TH_RUN; if (HistogramActive) { target_thread->ref_count++; } cpu_start(target_t.hread, psm); if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } context_switch(target_thread, thread, psm) ; return (thread_desc_t *) target_thread; } thread_desc_t *GetThread(void) { thread_desc_t *thread = NULL; if (ThreadListHead) { -41- SUBSTTIUTE SHEET (RULL 26) spin_lock(ThreadListMutex) ; if (ThreadListHead) { thread = ThreadListHead; ThreadListHead = thread->pset_threads_flink; if (ThreadListHead) ThreadListHead->pset_threads_blink = NULL; else ThreadListTail = NULL; thread->pset_threads_flink = (thread_desc_t *) NULL; thread->pset_threads_blink = (thread_desc_t *) NULL; } spin_unlock(ThreadListMutex) ; } if (FreeThreadElements) FreeThreadElements—; return (thread_desc_t *) thread; MutexAbend db 'Deadlock detected in lock mutex' mov [esi] .cthread_lock , 1 xor eax, eax CPop ret lockjmutex endp public m_try_lock m_try_lock proc Cpush ; save registers when called from C mov esi, [esp + ParmO] lock bts [esi] .mutex_lock, 0 -42- SUBSπTUTESHEfT(RULE26) jc /lock_failed xor eax, eax Cpop ret /lock_failed: mov eax, 1 Cpop ret /OnlyOneProcessor: we need compare for uni-processor case, else Abend mov [esi] .mutex_lock, 1 xor eax, eax Cpop ret m_try_lock endp -43- SUBSTITUTE 3!:E_T(!ΪUL£ 26) goto worker_start; } if (pset->active_task_head) { thread_switc () ; goto worker_start; } if (psm->processor_qhead) { thread_switch() ; goto worker_start; } if (psm->active_head) { psm->active_count = NULL; thread = (thread_desc_t *) psm->active_head; while (thread) { if (thread->thread_state ■== TH_WAIT) psm->active_count++; thread = thread->pset_threads_flink; } thread = (thread_desc_t *) psm->active_head; while (thread) { if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } switch (thread->thread_state) { case NULL: thread_switch() ; goto worker_start; break; case TH_WAIT: thread_switch() ; goto worker_start; break; case TH_RUN: thread_switch() ; goto worker_start; break; default: break; } switch (thread->halt_flag) { case TH_HALTED: thread_switch() ; goto worker_start; break; case TH_SWAPPED: thread_switch() ; goto worker_start; break; default: break; } thread = thread->pset_threads_flink; } goto worker_start; } _sti(); if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } LSLSMPSendProcedure() ; // import from NetWare if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } LSLSMPInternalPollProcedureO ; if (psm->current_address_space) { psm->current_address_space — NULL; _flush_tlb() ; } reaper_thread() ; if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } } } LONG mutex_destroy(mutex_t *mutex) { if (mutex->rtag !-= MutexSignature) { return (LONG) (EINVL) ; } mutex_sleep_free(mutex) ; return (LONG) (0) ; } LONG mutex_examine(mutex_t *mutex) { if (mutex->rtag != (struct ResourceTagStructure *) MutexSignature) { return (LONG) (EINVL) ; } return( (LONG) mutex->mutex_lock) ; } LONG mutex_init(mutex_t *mutex, LONG type, BYTE *Name) { if (mutex->rtag != MutexSignature) { return (LONG) (EINVL) ; } mutex->mutexname = Name; mutex->picrec_t = type; return (LONG) (0) ; } LONG mutex_link(mutex_t *mutex) { if (mutex->rtag 1= (struct ResourceTagStructure *) MutexSignature) { return (LONG) (EINVL) ; } _dis(); spin_lock(ActiveMutexList) ; mutex->mtxflink = &MutexHead; -45- SUBSTITUTE SH.EF (RULE 26) mutex->mtxblink = MutexHead.mtxblink; MutexHead.mtxblink->mtxflink = mutex; MutexHead.mtxblink = mutex; spin_unlock(ActiveMutexList) ; _en() ; return (LONG) (0) ; } LONG mutex_lock(mutex_t *mutex) { psm_t *psm; thread_desc_t *thread; thread_desc_t *t, *target_thread; processor_t *pset; if (mutex->rtag != (struct ResourceTagStructure *) MutexSignature) { return (LONG) (EINVL) ; } _cli(); psm = (psm_t *) psm_context() ; pset = (processor_t *) psm->processor_set; thread = (thread_desc_t *) psm->RunningThread; thread->last_processor = (LONG *) psm; atomic_inc( (LONG *) &mutex->mutex_waiters) ; if (m_try_lock(mutex) ) { lock_mutex(mutex) ; thread->thread_state = TH_SEMA; if ( lmutex->cthread_head) { mutex->cthread_head = (LONG *) thread; mutex->cthread_tail = (LONG *) thread; thread->sync_flink = (thread_desc_t *) NULL; thread->sync_blink = (thread_desc_t *) NULL; } else { t = (thread_desc_t *) mutex->cthread_tail; t->sync_flink = thread; thread->sync_flink = NULL; thread->sync_blink = (thread_desc_t *) mutex->cthread_tail; mutex->cthread_tail = (LONG *) thread; } mutex->cthread_mutex = 0; sema_the_threa (thread,psm) ; target_thread = get_target_and_switch(psm,thread) ; } mutex->mutex_owner = (LONG) thread; atomic_dec( (LONG *) &mutex->mutex_waiters) ; return (LONG) (0) ; } thread_desc_t *mutex_owner(mutex_t *mutex) { if (mutex->rtag 1= (struct ResourceTagStructure *) MutexSignature) { return (thread_desc_t *) (EINVL) ; } return ( (thread_desc_t *) mutex->mutex_owner) ; } -46- ITUTE SHEO (RULE 26) LONG mutex_priority(mutex_t *mutex) { if (mutex->rtag != (struct ResourceTagStructure *) MutexSignature) { return (LONG) (EINVL) ; } return ( (LONG) mutex->picrec_t) ; } mutex_t *mutex_sleep_alloc(BYTE *MutexName) { mutex_t *mutex; LONG timeout=0; while (! (mutex = GetMutex())) { SignalAndLinkMutex() ; if (timeout++ > 1000) { return (LONG) (0) ; } thr_yield() ; } mutex->mutexname = (BYTE *) MutexNa e; mutex->rtag = (struct ResourceTagStructure *) MutexSignature; mutex->cthread_head = 0; mutex->cthread_tail = 0; mutex->cthread_mutex = 0; mutex->mutex_lock = 0; mutex->mutex_type = SLEEP_TYPE; mutex_link(mutex) ; return (mutex_t *) mutex; } LONG mutex_sleep_free(mutex_t *mutex) { thread_desc_t *thread, *t; if (mutex->rtag !■= (struct ResourceTagStructure *) MutexSignature) { return (LONG) (EINVL) ; } mutex_unlink(mutex) ; lock_mutex(mutex) ; while (mutex->cthread_head) { thread = (thread_desc_t *) mutex->cthread_head; thread->thread_state = TH_WAIT; mutex->cthread_head = (LONG *) thread->sync_flink; if (mutex->cthread_head) { t = (thread_desc_t *) mutex->cthread_head; t->sync_blink = NULL; } else mutex->cthread_tail = NULL; thread->sync_flink = (thread_desc_t *) NULL; thread->sync_blink = (thread_desc_t *) NULL; rundown(thread) ; } mutex->cthread_mutex = 0; mutex->mutex_type = NULL; if (mutex) { -47- SUBSTTΠTE SHEET RULf 26 PutMutex(mutex) ; } return (LONG) (0) ; } LONG mutex_trylock(mutex_t *mutex) { thread_desc_t *thread; if (mutex->rtag != (struct ResourceTagStructure *) MutexSignature) { return (LONG) (EINVL) ; } _cli(); thread = thread_context() ; if ( !m_try_lock(mutex) ) { thread->thread_tpriority = thread->thread_priority; lock_mutex(mutex) ; mutex->mute " x_owner = (LONG) thread; mutex->picrec_t = thread->thread_priority; mutex->cthread_mutex = 0; return (LONG) (0) ; } return (LONG) (1) ; } LONG mutex_unlink(mutex_t *mutex) { if (mutex->rtag != (struct ResourceTagStructure *) MutexSignature) { return (LONG) (EINVL) ; } _dis(); spin_lock(ActiveMutexList) ; mutex->mtxflink->mtxblink = mutex->mtxblink; mutex->mtxblink->mtxflink -= mutex->mtxflink; spin_unlock(ActiveMutexList) ; _en() ; return (LONG) (0) ; } LONG mutex_unlock(mutex_t *mutex) { psm_t *psm; thread_desc_t *thread; thread_desc_t *t; processor_t *pset; if (mutex->rtag != (struct ResourceTagStructure *) MutexSignature) { return (LONG) (EINVL) ; } _cli(); psm = psm_context() ; pset = (processor_t *) psm->processor_set; thread = (thread_desc_t *) psm->RunningThread; thread->last_processor = (LONG *) psm; lock_mutex(mutex) ; if (!mutex->cthread_head) { mutex->mutex_lock = 0; -48- SUBSΠΠΠE SHEET (RDIE 26) mutex->cthread_mutex — 0 ; } else { thread = (thread_desc_t *) mutex->cthread_head; mutex->cthread_head = (LONG *) thread->sync_flink; if (mutex->cthread_head) { t = (thread_desc_t *) mutex->cthread_head; t->sync_blink = NULL; } else mutex->cthread_tail = NULL; thread->sync_flink = (thread_desc_t *) NULL; thread->sync_blink = (thread_desc_t *) NULL; mutex->cthread_mutex = 0; thread->thread_state = TH_WAIT; } return (LONG) (0) ; } public ProcessFastWorkToDos ProcessFastWorkToDos proc call sp n_unlock add esp, 4 DoNextFastWorkToDo: LOG_EVENT_l_PARM NW_SMP_KERNEL,SMP_FAST_WORK_TO_DO,ebp mov eax, [ebp] push eax push ebp -49- SUBSTITUTE SHEET (RUl£ 26) LONG processor_set_create(processor_t **new_set, BYTE *new_name) { processor_t *pset; pset = (processor_t *) Alloc(sizeof(processor_t) , (LONG) processor_t_RTag) ; if (Ipset) { return (LONG) (1) ; } CSetB((BYTE) 0x00, (LONG) pset, sizeof(processor_t) ) ; pset->active = TRUE; pset->ps_runq_mutex = mutex_sleep_alloc(MSG("processor_t runq mutex", 64) ) ; pset->idle_lock = mutex_sleep_alloc(MSG("processor_t idleq mutex", 65) ) ; pset->processor_mutex = mutex_sleep_alloc(MSG("processor_t processor mutex", 66)); pset->tasks_mutex = mutex_sleep_alloc(MSG("processor_t taskq mutex", 67)); pset->threads_mutex = mutex_sleep_alloc(MSG("processor_t threadq mutex", 68)); pset->ref_lock = mutex_sleep_alloc(MSG("processor_t ref lock mutex", 69) ) ; pset->pset_mutex = mutex_sleep_alloc(MSG("processor_t pset mutex", 70) ) ; pset->processor_t_lock = mutex_sleep_alloc(MSG("processor_t lock mutex", 71)); pset->ps_hpq_mutex = mutex_sleep_alloc(MSG("processor_t hi-pri mutex", 72)); pset->ps_mpq_mutex = mutex_sleep_alloc(MSG("processor_t md-pri mutex", 73)); pset->ps_lpq_mutex = mutex_sleep_alloc(MSG("processor_t low-pri mutex", 74)); pset->ps_qsq_mutex = mutex_sleep_alloc(MSG("processor_t quiesced mutex", 75)); pset->ps_lwq_mutex = mutex_sleep_alloc(MSG("processor_t low-pri poll mutex", 76)); pset->ps_fwq_mutex = mutex_sleep_alloc(MSG("processor_t fast wtd mutex", 77)); pset->ps_dwq_mutex = mutex_εleep_alloc(MSG("processor_t delayed wtd mutex", 78)); pset->ps_wtdq_mutex = mutex_sleep_alloc(MSG("processor_t work to do mutex", 79)); pset->ps_hipri_mutex = mutex_sleep_alloc(MSG("processor_t hi-pri poll mutex", 80)); pset->active_task_mutex = mutex_sleep_alloc(MSG("processor_t task mutex", 81)); pset->ps_name *-= new_name; pset->ps_fwq_head = 0; pset->ps_fwq_tail = (struct WorkToDoStructure *) &pset->ps_fwq_head; pset->ps_dwq_head = 0; pset->ps_dwq_tail = (struct WorkToDoStructure *) &pset->ps_dwq_head; pset->ps_wtdq_head = 0; pset->ps_wtdq_tail = (struct WorkToDoStructure *) &pset->ps_wtdq_head; pset->policies = POLICY_FULL_SMP J POLICY_RR; pset->max_priority = 100; *new_set = pset; _dis(); spin_lock(v_mutex) ; pset_lock(pset) ; if (lv_head) { v_head = pset; v_tail = pset; pset->set_flink = (processor_t *) NULL; pset->set_blink = (processor_t *) NULL; } else { v_tail->set_flink = pset; -51- SUBSΠTUTE SHEET (RUUE 26) pset->set_flink = (processor_t *) NULL; pset->set_blink = v_tail; v_tail = pset; } pset_unlock(pset) ; spin_unlock(v_mutex) ; _en() ; return (LONG) (0) ; } LONG pset_add_task(processor_t *pset, task_t *task) { _dis(); pset_task_lock(pset) ; task_lock(task) ; if (lpset->tasks_qhead) { pset->tasks_qhead = task; pset->tasks_qtail = task; task->pset_tasks_flink = (task_t *) NULL; task->pset_tasks_blink = (task_t *) NULL; pset->task_count++; task->processor_set = (LONG *) pset; task_unlock(task) ; pset_task_unlock(pset) ; _en() ; return (LONG) (0) ; } pset->tasks_qtail->pset_tasks_flink = task; task->pset_tasks_flink = NULL; task->pset_tasks_blink = pset->tasks_qtail; pset->tasks_qtail = task; pset->task_count++; task->processor_set = (LONG *) pset; task_unlock(task) ; pset_task_unlock(pset) ; _en(); return (LONG) (0) ; } LONG pset_add_thread(processor_t *pset, thread_desc_t *thread) { _dis(); pset_thread_lock(pset) ; thread_lock(thread) ; if (!pset->threads_qhead) { pset->threads_qhead = thread; pset->threads_qtail = thread; thread->thread_list_flink = (thread_desc_t *) NULL; thread->thread_list_blink = (thread_desc_t *) NULL; pset->thread_count++; thread->pcb_processor_set = (LONG *) pset; thread_unlock(thread) ; pset_thread_unlock(pset) ; _en() ; return (LONG) (0) ; } pset->threads_qtail->thread_list_flink = thread; thread->thread_list_flink = NULL; -52- SUBSΠΠΠE SHEET (RUIE 26) thread->thread_list_blink = pset->threads_qtail; pset->threads_qtail = thread; pset->thread_count++; thread->pcb_processor_set = (LONG *) pset; thread_unlock(thread) ; pset_thread_unlock(pset) ; _en() ; return (LONG) (0) ; } void pset_lock(processor_t *processor) { spin_lock(processor->processor_t_lock) ; return; } LONG pset_remove_task(processor_t *pset, task_t *task) { _dis(); pset_task_lock(pset) ; task_lock(task) ; if (pset->tasks_qhead == task) { pset->tasks_qhead - task->pset_tasks_flink; if (pset->tasks_qhead) { pset->tasks_qhead->pset_tasks_blink = NULL; } else pset->tasks_qtail = NULL; } else { task->pset_tasks_blink->pset_tasks_flink = task->pset_tasks_f1ink; if (pset->tasks_qtail 1= task) task->pset_tasks_flink->pset_tasks_blink = task->pset_tasks_blink; else pset->tasks_qtail = task->pset_tasks_blink; } pset->task_count—; task->processor_set = (LONG *) v_default; task_unlock(task) ; pset_task_unlock(pset) ; _en() ; return (LONG) (0) ; } LONG pset_remove_thread(processor_t *pset, thread_desc_t ♦thread) { _dis(); pset_thread_lock(pset) ; thread_lock(thread) ; if (pset->threads_qhead == thread) { pset->threads_qhead = thread->thread_list_flink; if (pset->threads_qhead) { pset->threads_qhead->thread_list_blink = NULL; } else -53- SUBSTITUTE SHEET (RUU 26 pset->threads_qtail = NULL; } else { thread->thread_list_blink->thread_list_flink = thread->thread_list_flink; if (pset->threads_qtail != thread) thread->thread_list_flink->thread_list_blink = thread->thread_list_blink; else pset->threads_qtail = thread->thread_list_blink; } pset->thread_count—; thread_unlock(thread) ; pset_thread_unlock(pset) ; _en() ; return (LONG) (0) ; } void pset_runq_lock(processor_t *pset) { spin_lock(pset->ps_runq_mutex) ; return; } LONG pset_runq_try_lock(processor_t *pset) { mutex_t *mutex; mutex = (mutex_t *) pset->ps_runq_mutex; if ( !mutex->mutex_lock) { spin_lock(pset->ps_runq_mutex) ; return (LONG) (0) ; } return (LONG) (1) ; } void pset_runq_unlock(processor_t *pset) { mutex_t *mutex; mutex = (mutex_t *) pset->ps_runq_mutex; mutex->mutex_lock = 0; return; } void pset_task_lock(processor_t *pset) { spin_lock(pset->tasks_mutex) ; return; } void pset_task_unlock(processor_t *pset) { pset->tasks_mutex->mutex_lock = 0; return; } void pset_thread_lock(processor_t *pset) { spin_lock(pset->threads_mutex) ; -54- SUBSTfTUTE SHEET (RULE 26) return; void pset_thread_unlock(processor_t *pset) pset->threads_mutex->mutex_lock = 0; return; void pset_unlock(processor_t *processor) processor->processor_t_lock->mutex_lock = 0; return; psm_t *psm_context(void) return (psm_t * ' ) v_psmtable[GetProcessorTable() ] ; void PutChainStack(stack_t *stack) if (stack->stack_rtag != (LONG) Stacksignature) { BREAK() ; } spin_lock(StackPoolListMutex) ; stack->thread_owner = NULL; stack->stack_flags = ST_FREE; if ( !StackPoolListHead) { StackPoolListHead = stack; StackPoolListTail = stack; stack->stflink = NULL; stack->stblink = NULL; } else { StackPoolListTail->stflink = stack; stack->stflink = NULL; stack->stblink = StackPoolListTail; StackPoolListTail = stack; } FreeStackElements++; spin_unlock(StackPoolListMutex) ; return; } void PutMutex(mutex_t *mutex) { spin_lock(MutexListMutex) ; if (!MutexListHead) { MutexListHead = mutex; MutexListTail = mutex; mutex->mtxflink = NULL; mutex->mtxblink = NULL; } else { MutexListTail->mtxflink = mutex; mutex->mtxflink = NULL; mutex->mtxblink = MutexListTail; -55- SUBSTTTUTE SHEET (RUL£26) MutexListTail = mutex; } FreeMutexElements-H-; spin_unlock(MutexListMutex) ; return; } void put_stack(thread_desc_t *thread) { stack_t *stack; stack = (stack_t *) thread->stack_chain; thread->stack_chain = NULL; thread->StackSP = NULL; PutChainStack( (stack_t *) stack); return; } void PutThread(thread_desc_t *thread) { spin_lock(ThreadListMutex) ; if ('ThreadListHead) { ThreadListHead = thread; ThreadListTail = thread; thread->pset_threads_flink - NULL; thread->pset_threads_blink = NULL; } else { ThreadListTail->pset_threads_flink = thread; thread->pset_threads_flink = NULL; thread->pset_threads_blink = ThreadListTail; ThreadListTail = thread; } thread->thread_state = TH_SWAPPED; thread->halt_flag = TH_SWAPPED; FreeThreadElements++; spin_unlock(ThreadListMutex) ; return; } void reaper_thread(void) d->thread_Cflink; rundown_tail = NULL; if (thread->thread_desc_task) { task_lock( (task_t *) thread->thread_desc_task) ; task_remove_thread( (task_t *) thread->thread_desc_task, thread) ; task_unlock( (task_t *) thread->thread_desc_task) ; } if (thread->pcb_processor_set) { pset_lock( (processor_t *) thread->pcb_processor_set) ; pset_remove_thread( (processor_t *) thread->pcb_processor_set, thread) ; pset_unlock( (processor_t *) thread->pcb_processor_set) ; } mutex_sleep_free( (mutex_t *)thread->thread_lock) ; mutex_sleep_free( (mutex_t *)thread->marshall_mutex) ; if ( (!thread->stack_flag) && ( !thread->CreatedInNetware) ) { put_stack(thread) ; } if (lthread->CreatedInNetware) { PutThread(thread) ; } else { thread->halt_flag = TH_SWAPPED; } } spin_unlock(rundown_mutex) ; _sti(); } if (task_rundown_head) { _cli(); spin_lock(task_rundown_mutex) ; if (task_rundown_head) { task = task_rundown_head; task_rundown_head = (task_t *) task->task_Cflink; if (task_rundown_head) { task_rundown_head->task_Cblink = NULL; } else task_rundown_tail = NULL; pset_lock( (processor_t *) task->processor_set) ; pset_remove_task( (processor_t *) task->processor_set, task) ; pset_unlock( (processor_t *) task->processor_set) ; mutex_sleep_free(task->task_lock) ; mutex_sleep_free(task->pset_tasks_mutex) ; mutex_sleep_free(task->thread_list_mutex) ; PutTask(task) ; } spin_unlock(task_rundown_mutex) ; _sti(); } } void rundown(thread_desc_t *thread) { spin_lock(rundown_mutex) ; if (!rundown_head) { rundown_head = thread; rundown_tail = thread; thread->thread_Cflink = (thread_desc_t *) NULL; thread->thread_Cblink = (thread_desc_t *) NULL; -57- SUBSTIIUTESHEET(RU1£26) } else { rundown_tail->thread_Cflink = thread; thread->thread_Cflink = NULL; thread->thread_Cblink = rundown_tail; rundown_tail = thread; } spin_unlock(rundown_mutex) ; return; } void sema_the_thread(thread_desc_t *thread, psm_t *psm) { thread_desc_t *t; thread->last_processor = (LONG *) psm; if ( !psm->active__head) { psm->active_head = (LONG *) thread; psm->active_tail = (LONG *) thread; thread->pset_threads_flink = (thread_desc_t *) NULL; thread->pset_threads_blink = (thread_desc_t *) NULL; } else { t = (thread_desc_t *) psm->active_tail; t->pset_threads_flink = thread; thread->pset_threads_flink = NULL; thread->pset_threads_blink = (thread_desc_t *) psm->active_tail; psm->active_tail = (LONG *) thread; } return; } void shutdown(thread_desc_t *target_thread, thread_desc_t ♦thread, psm_t *psm) { thread_desc_t *t; thread_unlock(target_thread) ; if (target_thread == (thread_desc_t *) psm->WorkerThread) { goto halt_engine; } if ( !psm->active_head) { psm->active_head = (LONG *) target_thread; psm->active_tail = (LONG *) target_thread; target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; } else { t = (thread_desc_t *) psm->active_tail; t->pset_threads_flink = target_thread; target_thread->pset_threads_flink = NULL; target_thread->pset_threads_blink = (thread_desc_t *) psm->active_tail; psm->active_tail = (LONG *) target_thread; } halt_engine: ; -58- 3UBSΩTUTE SHEET (RULE 26) while (psm->active_head) { t = (thread_desc_t *) psm->active_head; psm->active_head = (LONG *) t->pset_threads_flink; if (t->bound_processor) thread_bind(t, (psm_t *) &v_processor[0] ) ; else thread_start(t) ; } psm->active_head = NULL; psm->active_tail = NULL; psm->active_count = NULL; if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } flush_tlbs() ; context_haIt(thread) ; return; } void SignalAndChainStack(void) { if (spin_try_lock(ChainStackMutex) ) return; FailedStackCount++; CScheduleFastWorkToDo(&ChainStackWTD) ; return; } void SignalAndLinkMutex(void) { if (spin_try_lock(LinkMutexMutex) ) return; FailedMutexCount++; CScheduleFastWorkToDo(&LinkMutexWTD) ; return; } void SignalAndLinkTask(void) { if (spin_try_lock(LinkTaskMutex) ) return; FailedTaskCount++; CScheduleFastWorkToDo(SLinkTaskWTD) ; return; } void SignalAndLinkThread(void) { if (spin_try_lock(LinkThreadMutex) ) return; FailedThreadCount++; CScheduleFastWorkToDo(SLinkThreadWTD) ; return; } void SMPAddProcessor(LONG ProcessorNumber) { register int num; _cli(); num = (int) ProcessorNumber; if (num != 0) { (v_processor[num] .s_intr_init) (num) ; (v_processor[num] .s_timer_init) ( (LONG) schedule) ; } v_processor[num] .ProcessorState = PROCESSOR_RUNNING; v_processor[num] .RegisterWorkSpaceEAX = 0; SetCPUNumber(ProcessorNumber) ; CPU_Type[num] - GetCPUIDAndType() ; /if (SFT3SMP) if ((num == 1) && (sft3Flag == TRUE)) { GiveProcessorToMSEngine() ; } else { worker_thread(num) ; } /else /* (1SFT3SMP) */ worker_thread(niιm) ; /endif /* (1SFT3SMP) */ // // This call should never return // } void SMPMainThread(void) { psm_t *psm = (psm_t *) &v_processor[0] ; thread_desc_t *thread; processor_t *pset; LONG LowPriCount = 0; pset = (processor_t *) psm->processor_set; if (Ipset) { Abend(MSG("Worker Thread called with an invalid processor_t id", 1102)); } for (;;) { ServerUtilization->SCurrent++; if (psm->debugger_signal) { psm->debugger_signal = 0; EnterSMPDebugger(psm->ProcessorNumber) ; } if ( (lpsm->load_quantum) && ( (psm->load_average + ELIGIBLE_COUNT) < psm->active_count) ) { thread_switch() ; } if (pset->ps_runq_head) { thread_switch() ; } _cli(); if (!psm->ProcessorNumber) { if (FastWorkToDoListHead) { CYieldWithDelay() ; thread_switc () ; } if (psm->processor_qhead) { thread_switch() ; } if (psm->active_head) { psm->active_count = NULL; thread = (thread_desc_t *) psm->active_head; while (thread) { if (thread->thread_state == TH_WAIT) psm->active_count++; thread = thread->pset_threads_flink; } thread = (thread_desc_t *) psm->active_head; while (thread) { switch (thread->thread_state) { case NULL: thread_switch() ; goto process_polling; break; case TH_WAIT: thread_switch() ; goto process_polling; break; case TH_RUN: thread_switch() ; goto process_polling; break; default: break; } switch (thread->halt_flag) { case TH_HALTED: thread_switch() ; goto process_polling; break; case TH_SWAPPED: thread_switch() ; goto process_polling; break; default: break; } thread = thread->pset_threads_flink; } } process_polling: ; LSLSMPSendProcedure() ; LSLSMPInternalPollProcedure() ; reaper_thread() ; if (INetwareCriticalSectionFlag) { CYieldWithDelay() ; if (LowPriCount++ > 40) { LowPriCount = NULL; CRescheduleLastLowPriority() ; } } -61- SUBSTITUTE Si. f EET RUII LONG SMP_START(LONG Handle, LONG ScreenHandle, BYTE *CommandLine) { register int i; LONG GateCount; LONG *idt_ptr, idt; BYTE *addr, *ptr, *btmp, *swap; struct LoadDefinitionStructure *lds, *module; LONG *IDTTable, *tmp; ModuleHandle = (struct LoadDefinitionStructure *) Handle; SetCPUNumber(O) ; get_processor = (void (*) (void)) GetProcessorTable; SMPStubSize = (LONG) ((LONG) SMPRoutineSizeEnd - (LONG) MarshallToSMPRoutine) ; NetwareStubSize = (LONG) ( (LONG) NetwareRoutineSizeEnd - (LONG) MarshallToNWProcessRoutine) ; if (!strncmp(CommandLine, MSG("-m", 1) , 2) | | !strncmp(CommandLine, MSG("-M", 2), 2)) { mdisable = TRUE; OutputToScreen(0, MSG("Global Marshalling has been disabled\r\n", 294)); } else OutputToScreen(0, MSG("Global Marshalling has been enabled\r\n", 295) ) ; GateCount = 0x130; for (i=0; i < (32 + 32 + (32 * 19)); i++) { DebuggerTaskGateIndex[i] = GateCount; GateCount += 0x08; } dummyInternalOld.IPDLink = NULL; dummylnternal.IPDLink = NULL; dummyExternal.EPDLink = NULL; dummylnternalOld.IPDName = &dummy[0]; dummylnternal.IPDName = &dummy[0] ; dummyExternal.EPDName = &dummy[0]; CryptSymbolStruct(dummylnternalOld.IPDName) ; CryptSymbolStruct(dummylnternal.IPDName) ; CryptSymbolStruct(dummyExternal.EPDName) ; /if (SFT3SMP) if (GetServerConfigurationType() == IOEngineType) { sft3Flag = TRUE; } /endif /* (SFT3SMP) */ Ids = (struct LoadDefinitionStructure *) Handle; lds->LDFlags |= LDDontUnloadBit; SHandle = ScreenHandle; LargestStackSize = 132000; ViewIOAPIC = (void (*) ()) ImportPublicSymbol( (LONG) ModuleHandle, MSG("\x0A""ViewIOAPIC", 296));; View2ndIOAPIC = (void (*) ()) ImportPublicSymbol( (LONG) ModuleHandle, MSG("\xOD""View2ndIOAPIC", 1345));; ViewLocalAPIC = (void (*) ()) ImportPublicSymbol( (LONG) ModuleHandle, MSG("\x0D""ViewLocalAPIC", 1286));; Netframelnit = (void (*) ()) ImportPublicSymbol( (LONG) ModuleHandle, MSG("\x08""NETFRAME", 1336)); if (Netframelnit) { -62- SUBSmUTE SHEET (RULE 26) (♦Netframelnit) () ; } for (i=0; i < MAXIMUM_NUMBER_OF_PROCESSORS; i++) { v_psmtable[i] = (PSM *) &v_processor[i] ; v_processor[i] .ProcessorNumber = i; } (v_processor[0] .s_gdt) () ; (v_processor[0] .s_idt) () ; (v_processor[0] .s_pde) () ; InitializeInterruptTable() ; t p = (LONG *) &GlobalIDTPointer[2] ; IDTTable = (LONG *) *tmp; IDTAddress = (LONG) IDTTable; ptr = (BYTE *) &IDTTable[0 * 2]; addr = (BYTE *) &DivideErrorAddress; for (i=0; i < 2; i++) *addr++ = *ptr++; for (i=0; i < 4; i++) ptr++; for (i=0; i < 2; i++) *addr++ = *ptr++; ptr = (BYTE *) &IDTTable[0x29 * 2]; addr = (BYTE *) &KeyboardHandlerAddress; for (i=0; i < 2; i++) *addr++ = *ptr++; for (i=0; i < 4; i++) ptr++; for (i=0; i < 2; i++) *addr++ = *ptr++; /* Get the Task State Table Address */ bt p = (BYTE *) DivideErrorAddress; swap = (BYTE *) &TaskStateTable; TaskStateTable = NULL; swap[2] = btmp[7] ; swap[1] = btmp[6] ; swap[0] = btmp[5] ; globalbranchtag = AllocateResourceTag(Handle, MSG("SMP Global Branch Entry", 297), 0x47425445); setparm_alloc_RTag = AllocateResourceTag(Handle, MSG("SMP kernel setparm memory", 1368), AllocSignature) ; if ( !setparm_alloc_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 3)); return (LONG) (1) ; } setparm_RTag = AllocateResourceTag(Handle, MSG("SMP kernel setparm", 1401), SetableParameterSignature) ; if (!setparm_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 1402)); return (LONG) (1) ; } StackRTag = AllocateResourceTag(Handle, MSG("SMP kernel stacks", 298), AllocSignature); if (1StackRTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 1403)); return (LONG) (1) ; } cookie_t_RTag = AllocateResourceTag(Handle, MSG("SMP cookie_t blocks", 1358), AllocSignature); if (!cookie_t_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 1359)); -63- SUBSmUTE SHEET (RULI26 return (LONG) (1) ; } processor_t_RTag = AllocateResourceTag(Handle, MSG("SMP processor_t blocks", 4), AllocSignature); if ( !processor_t_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 5)); return (LONG) (1) ; } task_t_RTag = AllocateResourceTag(Handle, MSG("SMP task_t blocks", 6), AllocSignature); if (!task_t_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 7)); return (LONG) (1) ; } stack_t_RTag = AllocateResourceTag(Handle, MSG("SMP stack_t blocks", 8), AllocSignature); if (lstack_t_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 9)); return (LONG) (1) ; } thread_desc_t_RTag = AllocateResourceTag(Handle, MSG("SMP thread_desc_t blocks", 10), ProcessSignature); if ( !thread_desc_t_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 11)); return (LONG) (1) ; } pcb_t_RTag = AllocateResourceTag(Handle, MSG("SMP Process Control Blocks", 1360), ProcessSignature); if (_pcb_t_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 1361)); return (LONG) (1) ; } mutex_t_RTag = AllocateResourceTag(Handle, MSG("SMP mutex_t blocks", 12), AllocSignature); if (_mutex_t_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 13)); return (LONG) (1) ; } event_RTag = AllocateResourceTag(Handle, MSG("SMP cond_t blocks", 14), AllocSignature); if (levent_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 15)); return (LONG) (1) ; } sema_RTag = AllocateResourceTag(Handle, MSG("SMP sema_t blocks", 16), AllocSignature); if (!sema_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 17)); return (LONG) (1); } marshall_RTag = AllocateResourceTag(Handle, MSG("SMP Marshalling Tables", 118), AllocSignature); if (!marshall_RTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 94)); return (LONG) (1) ; } TimerRTag = AllocateResourceTag(Handle, MSG("SMP broadcast timer", 18), Timersignature) ; if (!TimerRTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate resource tag\r\n", 19)); return (LONG) (l) ; } EventRTag = AllocateResourceTag(Handle, MSG("SMP event notification", 20), 0x544E5645) ; if (!EventRTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 21)); return (LONG) (1) ; } WorkRTag = AllocateResourceTag(Handle, MSG("SMP work to do callbacks", 22), WorkCallBackSignature) ; if (! orkRTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 23)); return (LONG) (1) ; > PollingProcedureRTag = AllocateResourceTag(Handle, MSG("LSLPollingProcedure", 149), PollingProcedureSignature) ; if (!PollingProcedureRTag) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor resource tag\r\n", 1430)); return (LONG) (1) ; } // // From here we must perform cleanup // MKStackAlloc = (BYTE *) Alloc(8192, (LONG) StackRTag); if (!MKStackAlloc) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor stack for Netware. Down Server and Restart\r\n", 24) ) ; return (LONG) (1) ; } MKStack = (LONG) &MKStackAlloc[8192 - 8]; for (i=0; i < 32; i++) { ExceptionStackAlloc[i] = (BYTE *) Alloc(16000, (LONG) StackRTag) ; if (!ExceptionStackAlloc[i] ) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor stack for Netware. Down Server and Restart\r\n", 1380)); return (LONG) (1) ; } ExceptionStackfi] = (LONG) &ExceptionStackAlloc[i] [16000 - 8]; -65- SUBSITIUTE SHEET (RUL£ 26) CSetB((BYTE) 0x00, (LONG) ExceptionStackAlloc[i] , 16000) ; } for (i=0; i < MAX_DEBUGGER_STACKS; i++) { StackPoolAlloc[i] = (BYTE *) Alloc(16000, (LONG) StackRTag) ; if (!StackPoolAlloc[i]) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor stack for Netware. Down Server and Restart\r\n", 1339) ) ; return (LONG) (1) ; } StackPool[i] = (LONG) &StackPoolAlloc[i] [16000 - 8]; CSetB((BYTE) 0x00, (LONG) StackPoolAlloc[i] , 16000); } for (i=0; i < 32; i++) { INVLINTStackAllocfi] = (BYTE *) Alloc(8192, (LONG) StackRTag) ; if (!INVLINTStackAlloc[i]) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor stack for Netware. Down Server and Restart\r\n", 299) ) ; return (LONG) (1) ; } INVLINTStack[i] = (LONG) &INVLINTStackAlloc[i] [8192 - 8]; CSetB((BYTE) 0x00, (LONG) INVLINTStackAllocfi] , 8192); } for (i=0; i < 32; i++) { DoubleFaultStackAlloc[i] = (BYTE *) Alloc(4096, (LONG) StackRTag) ; if (!DoubleFaultStackAlloc[i]) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor stack for Netware. Down Server and Restart\r\n", 1342)); return (LONG) (1) ; } DoubleFaultStack[i] = (LONG) &DoubleFaultStackAlloc[i] [4096 - 8]; CSetB((BYTE) 0x00, (LONG) DoubleFaultStackAlloc[i] , 4096) ; } ProcessorTaskStackAlloc = (BYTE *) Alloc(4096, (LONG) StackRTag) ; if (!ProcessorTaskStackAlloc) { OutputToScreen(ScreenHandle, MSG("FATAL: could not allocate processor stack for Netware. Down Server and Restart\r\n", 467) ) ; return (LONG) (1) ; } ProcessorTaskStack = (LONG) &ProcessorTaskStackAlloc[4096 8]; CSetB((BYTE) 0x00, (LONG) ProcessorTaskStackAlloc, 4096); // // This is the point of no return; // InitializeSMPGDTO ; TwinkleToNetWareListMutex = mutex_alloc(MSG("smp twinkle list mutex", 25) ) ; TwinkleToNetWareWTDMutex = mutex_alloc(MSG("smp twinkle wtd mutex", 26) ) ; TimerListLock = mutex_alloc(MSG("smp timer mutex", 27)); rundown_mutex = mutex_alloc(MSG("process rundown list mutex", 28) ) ; task_rundown_mutex = mutex_alloc(MSG("task rundown list mutex", 29)); ThreadListMutex = mutex_alloc(MSG("thread list mutex", 30) ) ; MutexListMutex = mutex_alloc(MSG("mutex list mutex", 31)); SyncEventListMutex = mutex_alloc(MSG("sync event list mutex", 32) ) ; SemaphoreListMutex = mutex_alloc(MSG("semaphore list mutex", 33)); TaskListMutex = mutex_alloc(MSG("task list mutex", 34)); StackPoolListMutex = mutex_alloc(MSG("stack pool list mutex", 35) ) ; LinkThreadMutex = mutex_alloc(MSG("wtd link thread mutex", 36)); LinkSemaphoreMutex = mutex_alloc(MSG("wtd link semaphore mutex", 37) ) ; LinkTaskMutex = mutex_alloc(MSG("wtd link task mutex", 38)); LinkEventMutex = mutex_alloc(MSG("wtd link event mutex", 39)); LinkMutexMutex = mutex_alloc(MSG("wtd link mutex", 40)); ChainStackMutex = mutex_alloc(MSG("wtd chain stack mutex", 41)); ActiveMutexList = mutex_alloc(MSG("active mutex list", 42)); ActiveSemaphoreList = mutex_alloc(MSG("active semaphore list", 43)); ActiveEventList = mutex_alloc(MSG("active event list", 44)); MarshallListMutex = mutex_alloc(MSG("marshalling table mutex ", 95) ) ; ShutdownMutex = mutex_alloc(MSG("shutdown mutex ", 1381)); nlm_mutex = mutex_alloc(MSG("nlm bound list mutex " , 1419)); debug_mutex = mutex_alloc(MSG("debugger mutex ", 1420)); keyboard_mutex = mutex_alloc(MSG("keyboard mutex ", 1477)); parser_mutex = mutex_alloc(MSG("debug parser mutex ", 300)); alt_debug_mutex = mutex_alloc(MSG("alt parser mutex ", 1347)); InitializeKernelLists() ; *(LONG *) &TwinkleToNetWareWTD.WorkResourceTag = WorkRTag; EnterRealMode = RegisterForEventNotification( (struct ResourceTagStructure *) EventRTag, EVENT_CHANGE_TO_REAL_MODE, 0, NULL, (void (*) ()) GoToRealMode) ; ExitRealMode = RegisterForEventNotification( (struct ResourceTagStructure *) EventRTag, EVENT_RETURN_FROM_REAL_MODE, -67- BSΠTUTE SHEET (RULE 26) 0, NULL, (void (*) ()) ReturnFromRealMode) EventLoad = RegisterForEventNotification( (struct ResourceTagStructure *) EventRTag, EVENT_M0DULE_L0AD, 0, NULL, (void (*) ()) MarshallPublicValue) ; EventUnload = RegisterForEventNotification( (struct ResourceTagStructure *) EventRTag, EVENT_MODULE_UNLOAD, 0, NULL, (void (*) ()) UnloadPublicValue) ; EventDestroyProcess = RegisterForEventNotification( (struct ResourceTagStructure *) EventRTag, EVENT_DESTROY_PROCESS, 0, NULL, (void (*) ()) KillSMPProcess) ; EventCreateProcess = RegisterForEventNotification( (struct ResourceTagStructure *) EventRTag, EVENT_CREATE_PROCESS , 0 , NULL, (void (*)()) CreateSMPProcess) ; _dis(); GetExitToDOS() ; StartProcedure(Handle, ScreenHandle, (LONG) CommandLine) engine_init() ; MarshalllntemalPublicsO ; ProcessSwitchedPublics() ; /* Initialize Dirty Hook for the Loader to call us when RPCs are loaded */ if (HookProcessRPCData() != 0) { OutputToScreen(ScreenHandle, MSG("Can't Hook ProcessXDCData, its already hooked. Down Server and Restart\n\n", 1685) );\ return (2) ; } idt_ptr = (LONG *) &GlobalIDTPointer[2] ; idt = (LONG) *idt_ptr; OSPreemptValue = SetlnterruptTable( (LONG *) idt, (LONG) cPreemptionRoutine, (LONG) 0x28) ; OSPreemptValue = SetlnterruptTable( (LONG *) idt, (LONG) cPreemptionRoutine, (LONG) 0x48) ; default_vector = SetlnterruptTable( (LONG *) idt, (LONG) SpuriousInterrupt, (LONG) 0x30) ; SetlnterruptTable((LONG *) idt, (LONG) IntKeyboardlnterruptHandler, 0x29) ; en() -68- ____-_TmJTE SHEET (RUlf 26) thread_add_lowpri_poll(v_default, &lsl_polll, MSG("lεl send poller", 46), LSLSMPSendProcedure) ; thread_add_lowpri_poll(v_default, &lsl_poll2, MSG("lsl board poller", 47), LSLSMPInternalPollProcedure) ; thread_add_lowpri_poll(v_default, ϋreaper, MSG("reaper process", 48), reaper_thread) ; LoadAverageTimerNode.TResourceTag = (LONG) TimerRTag; LoadAverageTimerNode.TCallBackParameter = 0; LoadAverageTimerNode.TCallBackWaitTime = 18; LoadAverageTimerNode.TCallBackProcedure = (void (*)(LONG)) CalculateLoadAverage; ScheduleSMPTimerCallBack(..LoadAverageTimerNode) ; LoaderTimer.ProcedureToCall = (void (*) ()) LoaderTimerlnterrupt; SMPTimer.ProcedureToCall = (void (*) ()) cTi erProcedure; MigratePCB = (thread_desc_t *) CMakeProcess( (LONG) 0, (void (*) (void) )MigrateRoutine, ' (void *) ((LONG)&SMPStackl[0] + (LONG) 12288) , (LONG) 12288, (BYTE *) MSG("SMP Migrate", 97), (struct ResourceTagStructure *) thread_desc_t_RTag) ; MigratePCB->thread_state = TH_MIGRATE; MigratePCB->smpflag = TRUE; MigratePCB->MigrationStack = (LONG) MigratePCB->StackSP; SMPProcessID = CMakeProcess( (LONG) 0, (void (*) (void) ) SMPMainThread, (void *) ((LONG)&SMPStack[0] + (LONG) 12288) , (LONG) 12288, (BYTE *) MSG("SMP Idle", 110), (struct ResourceTagStructure *) thread_desc_t_RTag) ; set_in_netware_flag() ; InitParser(Handle) ; RegisterSetParameters() ; /* any NLMs who loaded prior to us cannot be allowed to pbind */ for (module = InternalModuleList; module != NULL; module = module->LDScanLink) { module->LDFlags &= -LDNLMCanBindBit; } SMPModifyGlobalBranchEntry(0, (LONG) EnterDebugger, EnterSMPDebugger, &function_address) ; SMPModifyGlobalBranchEntry(0, (LONG) RegisterAlternateKeyHandler, SMPRegisterAlternateKeyHandler, &original_register) ; SMPModifyGlobalBranchEntry(0, (LONG) UnRegisterAlternateKeyHandler, SMPUnRegisterAlternateKeyHandler, &original_unregister) ; /if (IDebugFix) SMPModifyGlobalBranchEntry(0, (LONG) RegisterDebugCommandParser, SMPRegisterDebugCommandParser, -69- SUBSTITUTE SHEET SME26 &original_register_debug) ; SMPModifyGlobalBranchEntry(0, (LONG) UnRegisterDebugCommandParser, SMPUnRegisterDebugCommandParser, &original_unregister_debug) ; /endif /* (IDebugFix) */ InitializeMe oryProtectionO ; return (LONG) (0) ; } ;************************************************************** ** ; spin_lock - waits an indefinite time period for a spin lock ; MUST be used with interrupts disabled ; Returns: ; WorkReg destroyed ; interrupts unchanged ; flags destroyed spin_lock macro MutexNode, SpinRoutine, WorkReg mov WorkReg, 1 if checkmutexfirst cmp [MutexNode] .mutex_lock, 0 jne SpinRoutine endif xchg [MutexNode] .mutex_lock, WorkReg ;asserts lock or WorkReg, WorkReg jnz SpinRoutine if mutexstats ine [MutexNode] .mutex_lock_count mov [MutexNode] .mutex_owner, $ endif &SpinRoutine&Return: endm *************************************************************** ** ; spin_lock_extra - used with spin_lock to spin & detect deadlock ; MUST be used with interrupts disabled ; Returns: ; WorkReg destroyed ; interrupts unchanged ; flags destroyed spin_lock_extra macro MutexNode, SpinRoutine, WorkReg &SpinRoutine&: ifidn <MutexNode>,<eax> ifidn <WorkReg>,<edx> push OFFSET -.SpinRoutine&Return jmp SpinLockCheckForDeadlock exitm endif endif push eax push edx mov eax, MutexNode mov edx, 1 call SpinLockCheckForDeadlock -70- SUSSHTUTE SHEET (RULE 26) pop edx pop eax jmp &SpinRoutine&Return endm *************************************************************** ** ; spin_try_lock - tries to engage the lock once, then returns a status code to indicate success or failure MUST be used with interrupts disabled returns: WorkReg destroyed interrupts unchanged spin_try_lock macro MutexNode, WorkReg, Failure if checkmutexfirst cmp [MutexNode] ,mutex_lock, 0 jne Failure * endif mov WorkReg, 1 xchg [MutexNode] .mutex_lock, WorkReg ;asserts lock or WorkReg, WorkReg jnz Failure if mutexstats mov [MutexNode] .mutex_owner, $ endif /failure: endm ;************************************************************** ** ; spin_unlock - unconditionally releases a spin lock ; MUST be used with interrupts disabled spin_unlock macro MutexNode lock dec [MutexNode] .mutex_lock endm void _sti(void) ; /pragma aux _sti = OxFB; // sti LONG task_assign(task_t *task, processor_t *new_pset, LONG assign_threads) { thread_desc_t *thread; processor_t *old_pset; _dis(); old_pset = (processor_t *) task->processor_set; task_change_psets(task, old_pset, new_pset) ; task_lock(task) ; if (assign_threads) { task_thread_lock(task) ; thread = task->thread_list_head; while (thread) { if (thread->pcb_processor_set 1= (LONG *) new_pset) { thread_change_psets(thread, (processor_t *) thread->pcb_processor_set, new_pset) ; } -71- thread = thread->thread_list_flink; } task_thread_unlock(task) ; } task_unlock(task) ; _en() ; return (LONG) (0) ; } LONG task_create(task_t *parent_task, LONG inherit_memory, task_t **child_task, LONG domain, BYTE *task_name) { task_t *task; processor_t *pset; while (!(task = GetTaskQ)) { SignalAndLinkTask() ; thread_switch() ; } CSetB(0x00, (LONG) task, sizeof(task_t) ) ; task->task_lock = mutex_sleep_alloc(MSG("task_t mutex", 82)); task->pset_tasks_mutex = mutex_sleep_alloc(MSG("task_t pset mutex", 83) ) ; task->thread_list_mutex = mutex_sleep_alloc(MSG("task_t thread mutex", 84)); if (inherit_memory) task->vm_domain_map = parent_task->vm_domain_map; else if (domain) task->vm_domain_map = domain; else task->vm_domain_map ■= OSDomain; task->active = TRUE; if (parent_task) { task_lock(parent_task) ; pset - (processor_t *) parent_task->processor_set; if ( !pset->active) { pset = v_default; } task->priority = parent_task->priority; task_unlock(parent_task) ; } else { pset = v_default; task->priority = 100; } task->task_name = task_name; task->task_pset_default = (LONG *) v_default; pset_lock(pset) ; pset_add_task(pset, task); pset_unlock(pset) ; *child_task = task; return (LONG) (0) ; } LONG task_change_psets(task_t *task, processor_t *old_pset, processor_t *new_pset) { pset_remove_task(old_pset, task) ; pset_add_task(new_pset, task) ; return (LONG) (0) ; } void task_lock(task_t *task) { spin_lock(task->task_lock) ; return; } LONG task_policy(task_t *task, LONG policy, LONG change_threads) { thread_desc_t *thread; task_lock(task) ; task->policy = policy; if (change_thre ' ads) { task_thread_lock(task) ; thread = task->thread_list_head; while (thread) { if (thread->thread_policy != policy) { thread_lock(thread) ; thread->thread_policy = policy; thread_unlock(thread) ; } thread = thread->thread_list_flink; } task_thread_unlock(task) ; } task_unlock(task) ; return (LONG) (0) ; } LONG task_priority(task_t *task, LONG priority, LONG change_threads) { thread_desc_t *thread; task_lock(task) ; task->priority = priority; if (change_threads) { task_thread_lock(task) ; thread = task->thread_list_head; while (thread) { if (thread->thread_priority < priority) { thread_lock(thread) ; thread->thread_priority = priority; thread_unlock(thread) ; } thread = thread->thread_list_flink; } task_thread_unlock(task) ; } task_unlock(task) ; return (LONG) (0) ; } void task_thread_lock(task_t *task) spin_lock(task->thread_list_mutex) return; void task_thread_unlock(task_t *task) task->thread_list_mutex->mutex_lock = 0; return; void task_unlock(task_t *task) task->task_lock->mutex_lock = 0; return; LONG thread_change_psets(thread_desc_t *thread, processor_t *old_pset, processor_t *new_pset) { pset_remove_thread(old_pset, thread) ; pset_add_thread(new_pset, thread) ; return (LONG) (0) ; } thread_desc_t *thread_context(void) { LONG processornumber; -74- SUBSmUTE SHEET RULE thread_desc_t *thread; _dis(); processornumber = GetProcessorTable() ; if ( (v_psmtable[processornumber] ) ->RunningThread == (LONG) NetWareContext) thread = (thread_desc_t *) RunningProcess; else thread = (thread_desc_t *) (v_psmtable[processornumber] ) ->RunningThread; _en() ; return (thread_desc_t *) thread; } LONG thread_create(task_t *task, thread_desc_t **thread, BYTE ♦name, LONG stacksize, LONG *parms, void (*start) ()) { register int i, count=0; thread_desc_t *new_thread; mutex_t *mutex; LONG *ptr; task = task; count = 0; while ( ! (new_thread = GetThread() ) ) { SignalAndLinkThread() ; if (count++ > 1000) return (LONG) (-1) ; thr_yield() ; } ptr = (LONG *) mutex_sleep_alloc(MSG("thread_desc_t mutex", 1308) ) ; if (!ptr) { return (LONG) (-1) ; } new_thread->thread_lock = ptr; ptr = (LONG *) mutex_sleep_alloc(MSG("rpc mutex", 1309)); if (Iptr) { mutex_sleep_free( (mutex_t *)new_thread->thread_lock) ; return (LONG) (-1) ; } new_thread->marshall_mutex = ptr; mutex = (mutex_t *) new_thread->marshall_mutex; mutex->mutex_lock = TRUE; new_thread->thread_priority = 100; new_thread->thread_state = TH_UNINT; new_thread->thread_stackεize = stacksize; new_thread->thread_parms = parms; new_thread->thread_callback = start; new_thread->bound_processor = NULL; new_thread->ref_count = NULL; new_thread->may_assign = NULL; new_thread->assign_active = NULL; new_thread->thread_policy = SCHED_FIFO; new_thread->halt_flag = FALSE; new_thread->CreatedInNetware = FALSE; new_thread->PCBRTag = (struct ResourceTagStructure *) thread_desc_t_RTag; new_thread->fault_flag = FALSE; new_thread->cpu_time = NULL; -75- SUE-STTTUTE SHEET (RULE 26) new_thread->netware_affinity = NULL; for (i=0; i < 16; i++) { new_thread->ProcessName[i] = (BYTE) 0x00; } for (i=0; (i < 16) && (name[i]); i++) { new_thread->ProcessName[i] = name[i]; } new_thread->ProcessName[16] = '\0' ; pset_lock(v_default) ; pset_add_thread(v_default, new_thread) ; pset_unlock(v_default) ; new_thread->last_processor = (LONG ♦) &v_processor[0] ; count = 0; while (get_stack(new_thread) ) { SignalAndChainStack() ; if (count-H- > 1000) { PutThread(new_thread) ; return (LONG) (-1) ; } thr_yield() ; }; LOG_EVENT_5_PAR ( NW_SMP_KERNEL, SMP_THREAD_CREATE, new_thread, name[0]«21 J name[l]«l4 J name[2]«7 [ name[3], name[4]«21 | name[5]«14 J name[6]«7 | name[7] , name[8]«21 J name[9]«14 ] name[10]«7 name[11] , name[12]«21| name[13]«14 J name[14]«7| name[15]); ♦thread = new_thread; return (LONG) (0) ; } void thread_lock(thread_desc_t ♦thread) { spin_lock( (mutex_t ♦ ) thread->thread_lock) ; return; } LONG thread_start(thread_desc_t ♦thread) { processor_t ♦pset; thread_desc_t *t ; pset = (processor_t ♦) thread->pcb_processor_set; pset_runq_lock(pset) ; thread_lock(thread) ; if (!pset->ps_runq_head) { pset->ps_runq_head = (thread_desc_t ♦) thread; pset->ps_runq_tail = (thread_desc_t ♦) thread; thread->pset_threads_flink = (thread_desc_t ♦) NULL; thread->pset_threads_blink = (thread_desc_t ♦) NULL; } else { t = (thread_desc_t ♦) pset->ps_runq_tail; t->pset_threads_flink = thread; thread->pset_threads_flink = NULL; -76- SUBSTTTUTE SHEET (RULE26) thread->pset_threads_blink = (thread_desc_t ♦) pset->ps_runq_tail; pset->ps_runq_tail = (thread_desc_t *) thread; } thread->last_processor - (LONG *) &v_processor[0] ; thread_unlock(thread) ; pset_runq_unlock(pset) ; LOG_EVENT_l_PARM( NW_SMP_KERNEL, SMP_THREAD_START, thread); return (LONG) (0) ; } void thread_switch(void) ; thread = (thread_desc_t *) psm->RunningThread; // is the thread non-preemptable ? if ( (thread->thread_policy & SCHED_FIFO) == 0) { _sti() ; return; } cpu_stop(thread, psm) ; pset = (processor_t *) psm->processor_set; thread->last_processor = (LONG *) psm; temp = (thread_desc_t ♦) NULL; target_thread = (thread_desc_t ♦) NULL; task = (task_t ♦) NULL; check_debugger(psm) ; if ( !psm->ProcessorNumber) { if (FastWorkToDoListHead) { ProcessFastWorkToDos() ; } } if (psm->LowPriorityPollCount++ > LOW_PRIORITY_POLL_COUNT) { psm->LowPriorityPollCount = 0; if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } LSLSMPSendProcedure() ; if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } LSLSMPInternalPollProcedure() ; if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } reaper_thread() ; if (psm->current_address_space) { psm->current_address_space - NULL; _flush_tlb() ; } } if (psm->processor_qhead) { spin_lock( (mutex_t ♦) psm->processor_qmutex) ; while (psm->processor_qhead) { target_thread = (thread_desc_t ♦) psm->processor_qhead; psm->processor_qhead = (LONG ♦) target_thread->pset_threads_flink; if (psm->processor_qhead) { t = (thread_desc_t ♦) psm->processor_qhead; t->pset_threads_blink = NULL; } else psm->processor_qtail = NULL; target_thread->pset_threads_flink = (thread_desc_t ♦) NULL; target thread->pset threads blink = (thread desc_t ♦) NULL; target_thread->assign_active = FALSE; target_thread->thread_state = TH_WAIT; if ( !psm->active_head) { psm->active_head = (LONG ♦) target_thread; psm->active_tail = (LONG ♦) target_thread; target_thread->pset_threads_flink = (thread_desc_t ♦) NULL; target_thread->pset_threads_blink = (thread_desc_t ♦) NULL; } else { t = (thread_desc_t ♦) psm->active_tail; t->pset_threads_flink = target_thread; target_thread->pset_threads_flink = NULL; target_thread->pset_threads_blink = (thread_desc_t ♦) psm->active_tail; psm->active_tail = (LONG ♦) target_thread; } } spin_unlock( (mutex_t ♦) psm->processor_qmutex) ; } if (pset->active_task_head) { spin_lock(pset->active_task_mutex) ; if (pset->active_task_head) { task = pset->active_task_head; pset->active_task_head = (task_t ♦) task->task_flink; if (pset->active_task_head) pset->active_task_head->task_blink = NULL; else pset->active_task_tail = NULL; task->task_flink = (task_t ♦) NULL; task->task_blink = (task_t ♦) NULL; } spin_unlock(pset->active_task_mutex) ; _flush_tlb() ; } if (psm->p_runq_count) psm->p_runq_count—; else { if (pset->ps_runq_head) { pset_runq_lock(pset) ; if (pset->ps_runq_head) { target_thread = pset->ps_runq_head; if ( (target_thread->thread_state == TH_MIGRATE) I I (target_thread->thread_state == TH_SEMA) ] j (target_thread->thread_state == TH_DELAY) ) { pset->ps_runq_head = (thread_desc_t ♦) target_thread->pset_threads_f link; if (pset->ps_runq_head) pset->ps_runq_head->pset_threads_blink = NULL; else pset->ps_runq_tail = NULL; target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; if ( lpsm->active_head) { psm->active_head = (LONG *) target_thread; psm->active_tail ■= (LONG *) target_thread; target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; } else { t = (thread_desc_t *) psm->active_tail; t->pset_threads_flink = target_thread; target_thread->pset_threads_flink = NULL; target_thread->pset_threads_blink = (thread_desc_t *) psm->active_tail; psm->active_tail = (LONG *) target_thread; } } else if (target_thread->StackSP) { thread_lock(target_thread) ; pset->ps_runq_head = (thread_desc_t *) target_thread->pset_threads_flink; if (pset->ps_runq_head) pset->ps_runq_head->pset_threads_blink = NULL; else pset->ps_runq_tail = NULL; target_thread->pset_threads_flink = (thread_desc_t *) NULL; target_thread->pset_threads_blink = (thread_desc_t *) NULL; pset_runq_unlock(pset) ; _flush_tlb() ; goto reschedule_exit; } } pset_runq_unlock(pset) ; -79- BSTΠUTESHEET RUl£26) _flush_tlb() ; } } if (psm->active_head) { psm->active_count = NULL; target_thread = (thread_desc_t *) psm->active_head; while (target_thread) { if (target_thread->thread_state == TH_WAIT) psm->active_count++; target_thread = target_thread->pset_threads_flink; } target_thread = (thread_desc_t ♦) psm->active_head; while (target_thread) { if (target_thread->halt_flag == TH_HALTED) { if (psm->active_head == (LONG ♦) target_thread) { psm->active_head = (LONG ♦) target_thread->pset_threads_flink; if (psm->active_head) { t = (thread_desc_t ♦) psm->active_head; t->pset_threads_blink = NULL; } else psm->active_tail = NULL; } else { target_thread->pset_threads_blink->pset_threads_flink = target_thread->pset_threads_flink; if (target_thread 1= (thread_desc_t ♦) psm->active_tail) target_thread->pset_threads_flink->pset_threads_blink = target_thread->pset_threads_blink; else psm->active_tail = (LONG ♦) target_thread->pset_threads_blink; } target_thread->pset_threads_flink = (thread_desc_t ♦) NULL; target_thread->pset_threads_blink = (thread_desc_t ♦) NULL; rundow (target_thread) ; target_thread = (thread_desc_t ♦) psm->active_head; continue; } else if ( (target_thread->thread_state != TH_MIGRATE) && (target_thread->thread_state != TH_SEMA) && (target_thread->thread_state != TH_DELAY) && (target_thread->StackSP != 0) ) { if (psm->active_head == (LONG *) target_thread) { psm->active_head = (LONG *) target_thread->pset_threads_flink; if (psm->active_head) { t — (thread_desc_t *) psm->active_head; t->pset_threads_blink = NULL; } else psm->active_tail = NULL; } else { target_thread->pset_threads_blink->pset_threads_flink = target_thread->pset_threads_flink; if (target_thread != (thread_desc_t *) psm->active_tail) target_thread->pset_threads_flink->pset_threads_blink = target_thread->pset_threads_blink; else psm->active_tail = (LONG *) target_thread->pset_threads_blink; } target_thread->pset_threads_flink = (thread_desc_t ♦ ) NULL; target_thread->pset_threads_blink = (thread_desc_t ♦) NULL; target_thread->assign_active = TRUE; goto reschedule_exit; } target_thread = target_thread->pset_threads_flink; } } target_thread = (thread_desc_t ♦) psm->WorkerThread; reschedule_exit: ; if (task) { if ( !psm->task_head) { psm->task_head = (LONG ♦) task; psm->task_tail = (LONG ♦) task; task->task_flink = (task_t ♦) NULL; task->task_blink = (task_t ♦) NULL; } else { ts = (task_t ♦) psm->task_tail; ts->task_flink = task; task->task_flink = NULL; task->task_blink = (task_t ♦) psm->task_tail; psm->task_tail = (LONG ♦) task; } } if (thread == (thread_desc_t ♦) psm->WorkerThread) { goto do_context; } if (thread->halt_flag == TH_HALTED) { spin_lock(rundown_mutex) ; if ( lrundown_head) { rundown_head = thread; rundown_tail = thread; thread->thread_Cflink = (thread_desc_t ♦) NULL; thread->thread_Cblink = (thread_desc_t ♦) NULL; } else { rundown_tail->thread_Cflink = thread; thread->thread_Cflink = NULL; thread->thread_Cblink = rundown_tail; rundown_tail = thread; } spin_unlock(rundown_mutex) ; goto do_context; -81- SUBSTiniTE SHEET (RULE 26) } if (thread->bound_processor) { binding = (psm_t ♦) thread->bound_processor; thread->assign_active = FALSE; thread->thread_state = TH_WAIT; if ( !binding->active_head) { binding->active_head = (LONG *) thread; binding->active_tail = (LONG ♦) thread; thread->pset_threads_flink = (thread_desc_t ♦) NULL; thread->pset_threads_blink = (thread_desc_t ♦) NULL; } else { t = (thread_desc_t ♦) binding->active_tail; t->pset_threads_flink = thread; thread->pset_threads_flink = NULL; thread->pset_threads_blink = (thread_desc_t ♦) binding->active_tail; binding-">active_tail = (LONG ♦) thread; } goto do_context; } if ( (!psm->load_quantum) && ( (psm->load_average + ELIGIBLE_COUNT) < psm->active_count) ) { psm->p_runq_count += 4; pset_runq_lock(pset) ; thread_lock(thread) ; thread->assign_active = FALSE; thread->thread_state = TH_WAIT; if ( !pset->ps_runq_head) { pset->ps_runq_head = (thread_desc_t ♦) thread; pset->ps_runq_tail = (thread_desc_t ♦) thread; thread->pset_threads_flink = (thread_desc_t ♦) NULL; thread->pset_threads_blink = (thread_desc_t ♦) NULL; } else { pset->ps_runq_tail->pset_threads_flink = thread; thread->pset_threads_flink = NULL; thread->pset_threads_blink = (thread_desc_t ♦) pset->ps_runq_tail; pset->ps_runq_tail = (thread_desc_t ♦) thread; } pset_runq_unlock(pset) ; _flush_tlb() ; goto do_context; } thread->assign_active = FALSE; thread->thread_state = TH_WAIT; if ( !psm->active_head) { psm->active_head = (LONG ♦) thread; psm->active_tail = (LONG ♦) thread; thread->pset_threads_flink = (thread_desc_t ♦) NULL; thread->pset_threads_blink = (thread_desc_t ♦) NULL; } else { t = (thread_desc_t ♦ ) psm->active_tail; t->pset_threads_flink = thread; thread->pset_threads_flink = NULL; -82- SUE-SmUTE SHEET (RULE 26) thread->pset_threads_blink = (thread_desc_t ♦) psm->active_tail; psm->active_tail = (LONG *) thread; } //*************************** // DEBUG CODE if (psm->active_tail) { t = (thread_desc_t *) psm->active_tail; if (psm->active_head != psm->active_tail) { if (!t->pset_threads_blink) BREAK() ; } } // DEBUG CODE //*************************** do_context: ; if (psm->ProcessorNumber) { if (psm->ProcessorState == PROCESSOR_SHUTDOWN) { shutdown(target_thread, thread, psm) ; return; } } psm->load_quantum++; psm->util_load = psm->load_quantum; target_thread->thread_state = TH_RUN; if (HistogramActive) { target_thread->ref_count++; } cpu_start(target_thread, psm) ; if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } context_switch(target_thread, thread, psm); return; } void thread_unlock(thread_desc_t *thread) { mutex_t ♦mutex; mutex = (mutex_t *) thread->thread_lock; mutex->mutex_lock = 0; return; } void thr_yield(void) { thread_desc_t *thread; thread = thread_context() ; if (thread->smpflag) thread_switch() ; else CYieldIfNeeded() ; return; } /*** worker_thread - idle loop for the SMP Kernel; where the ♦ micro-kernel spends the majority of its time. ♦ / void worker_thread(LONG engine) { -83- SUBSTTTUTE SHEET (RULE 26) thread_desc_t ♦thread; -84- SUBSTΓΠΠΈ SHEET (RULE 26) while (thread) { if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } switch (thread->thread_state) { case NULL: thread_switch() ; goto worker_start; break; case TH_WAIT: thread_switch() ; goto worker_start; break; case TH_RUN: thread_switch() ; goto worker_start; break) default: break; } switch (thread->halt_flag) { case TH_HALTED: thread_switch() ; goto worker_start; break; case TH_SWAPPED: thread_switch() ; goto worker_start; break; default: break; } thread = thread->pset_threads_flink; } goto worker_start; } _sti(); if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } LSLSMPSendProcedure() ; // import from NetWare if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } LSLSMPInternalPollProcedureO ; if (psm->current_address_space) { psm->current_address_space = NULL; _flush_tlb() ; } reaper thread() ; -85- SUBSTITUTE SHEE (RULE 26)

Claims: CLAIMS 1. A method for scheduling the execution of a plurality of threads on a plurality of processors in a computer system, said method comprising the steps of: associating an unlocked local queue of threads with each of the processors; maintaining a global dispatch queue of threads which are not presently associated with any processor; selecting movable threads from the unlocked local queues; and on.each of the processors, yielding control of the processor to a thread that is selected from threads in the global dispatch queue and the unlocked local queue of the processor. 2. The method of claim 1, further comprising the step of moving at least one of the movable threads from its unlocked local queue to the global dispatch queue. 3. The method of claim 2, wherein the global dispatch queue is a lockable queue to prevent it from being changed by more than one thread at a time, and said step of moving at least one of the threads comprises the steps of: locking the global dispatch queue; deleting the selected thread from its unlocked local queue; inserting the selected thread in the global dispatch queue; and unlocking the global dispatch queue. 4. The method of claim 1, wherein said step of selecting movable threads comprises identifying a busiest processor among the plurality of processors, the movable threads being selected only from eligible threads in the unlocked local queue of the busiest processor. 5. The method of claim 4, wherein said identifying step comprises identifying as a busiest processor a processor which has received the smallest number of sleep requests of any of the processors during a sampling period. -86- .-JE-STπUTE SHEET (RULE 26) 6. The method of claim 1, wherein said step of selecting movable threads comprises identifying a busiest popular processor among the plurality of processors, a processor being popular when its unlocked local queue contains at least two eligible threads, the movable threads being selected only from eligible threads in the unlocked local queue of the busiest popular processor. 7. The method of claim 6, wherein said identifying step comprises identifying as a busiest popular processor a popular processor which has received the smallest number of sleep requests of any of the popular processors during a sampling period. . 8. The method of claim 1, wherein on each of the processors said step of yielding control of the processor to a thread comprises the steps of searching in a predetermined order at least a portion of the global dispatch queue and the unlocked local queue of the processor to locate an eligible thread and yielding control of the processor to an eligible thread found during said searching step. 9. The method of claim 8, wherein said yielding step comprises yielding control to the first eligible thread found during said searching step. 10. The method of claim 8, wherein said yielding step comprises currently yielding control to an eligible thread found in the global dispatch queue, wherein during a previous yielding step control was yielded to an eligible thread found at that time in the global dispatch queue, and wherein control has been yielded at least once to a thread in the unlocked local queue between said previous yielding step and said step of currently yielding control. 11. The method of claim 8, wherein said searching step comprises the steps of checking the global dispatch queue for an eligible thread and checking the unlocked local queue of the processor if no eligible thread is found in the global dispatch queue. 12. The method of claim 1, wherein on each of the processors said step of yielding control of the processor to a thread comprises the steps of: -87- RU_E26 associating a lockable local queue of threads with each of the processors; searching in a predetermined order at least a portion of the global dispatch queue, the unlocked local queue of the processor, and the lockable local queue of the processor to locate an eligible thread; and yielding control of the processor to an eligible thread found during said searching step. 13. The method of claim 12, wherein said yielding step comprises yielding control to the first eligible thread found during said.searching step. 14. The method of claim 12, wherein said searching step comprises: checking the lockable local queue of the processor for an eligible thread; checking the global dispatch queue if no eligible thread is found in the lockable local queue; and checking the unlocked local queue if no eligible thread is found in the global dispatch queue. 15. The method of claim 12, wherein said searching step comprises: checking the lockable local queue of the processor for an eligible thread; determining whether checking the global dispatch queue will exceed a predetermined relative frequency for global dispatch queue accesses; checking the global dispatch queue if such checking will not exceed the predetermined relative frequency for global dispatch queue accesses and if no eligible thread is found in the lockable local queue; and checking the unlocked local queue if no eligible thread is found in the global dispatch queue. 16. A computer-readable storage medium having a configuration that represents data and instructions which cause a multiprocessor to perform method steps for scheduling threads, the method comprising the steps of: -88- SUBSTTTUTE SHEET (B1__IE26) associating an unlocked local queue of threads with each of the processors; maintaining a global dispatch queue of threads which are not presently associated with any processor; selecting movable threads from the unlocked local queues; and on each of the processors, yieldinq control of the processor to a thread that is selected from the threads in the global dispatch queue and the unlocked local queue of the processor. 17. The. storage medium of claim 16, wherein the method further comprises the step of moving at least one of the movable threads from its unlocked local queue to the global dispatch queue. 18. The storage medium of claim 17, wherein the global dispatch queue is a lockable queue to prevent it from being changed by more than one thread at a time, and said step of moving at least one of the threads comprises the steps of: locking the global dispatch queue; deleting the selected thread from its unlocked local queue; inserting the selected thread in the global dispatch queue; and unlocking the global dispatch queue. 19. The storage medium of claim 16, wherein said step of selecting movable threads comprises identifying a busiest processor among the plurality of processors, the movable threads being selected only from eligible threads in the unlocked local queue of the busiest processor. 20. The storage medium of claim 19, wherein said identifying step comprises identifying as a busiest processor a processor which has received the smallest number of sleep requests of any of the processors during a sampling period. 21. The storage medium of claim 16, wherein said step of selecting movable threads comprises identifying a busiest popular processor among the plurality of processors, a processor being popular when its unlocked local queue contains at least two eligible threads, the movable threads being selected only from eligible threads in the unlocked local queue of the busiest popular processor. 22. The storage medium of claim 21, wherein said identifying step comprises identifying as a busiest popular processor a popular processor which has received the smallest number of sleep requests of any of the popular processors during a sampling period. 23. The storage medium of claim 16, wherein on each of the processors said step of yielding control of the processor to a thread comprises the steps of: searching in a predetermined order at least a portion.of the global dispatch queue and the unlocked local queue of the processor to locate an eligible thread; and yielding control of the processor to an eligible thread found during said searching step. 24. The storage medium of claim 23, wherein said yielding step comprises yielding control to the first eligible thread found during said searching step. 25. The storage medium of claim 23, wherein said yielding step comprises currently yielding control to an eligible thread found in the global dispatch queue, wherein during a previous yielding step control was yielded to an eligible thread found at that time in the global dispatch queue, and wherein control has been yielded at least once to a thread in the unlocked local queue between said previous yielding step and said step of currently yielding control. 26. The storage medium of claim 23, wherein said searching step comprises the steps of: checking the global dispatch queue for an eligible thread; and checking the unlocked local queue of the processor if no eligible thread is found in the global dispatch queue. 27. The storage medium of claim 16, wherein on each of the processors said step of yielding control of the processor to a thread comprises the steps of: associating a lockable local queue of threads with each of the processors; searching in a predetermined order at least a portion of the global dispatch queue, the unlocked local queue of the processor, and the lockable local queue of the processor to locate an eligible thread; and yielding control of the processor to an eligible thread found during said searching step. 28. The storage medium of claim 27, wherein said yielding step comprises yielding control to the first eligible thread found during said searching step. 29. The storage medium of claim 27, wherein said searching step comprises: checking the lockable local queue of the processor for an eligible thread; checking the global dispatch queue if no eligible thread is found in the lockable local queue; and checking the unlocked local queue if no eligible thread is found in the global dispatch queue. 30. The storage medium of claim 27, wherein said searching step comprises: checking the lockable local queue of the processor for an eligible thread; determining whether checking the global dispatch queue will exceed a predetermined relative frequency for global dispatch queue accesses; checking the global dispatch queue if such checking will not exceed the predetermined relative frequency for global dispatch queue accesses and if no eligible thread is found in the lockable local queue; and checking the unlocked local queue if no eligible thread is found in the global dispatch queue. 31. An apparatus for scheduling the execution of threads, comprising: a plurality of processors for executing instructions; a shared memory for holding data; -91- a bus connecting said processors with said shared memory such that each of said processors can read and write locations within said shared memory; an unlocked local queue of threads associated with each of said processors; a global dispatch queue of threads which are not presently associated with any of said processors; means for selecting movable threads from said unlocked local queues; and on each of said processors, means for yielding control of said processor to a thread that is selected from the threads in said global dispatch queue and said unlocked local queue of said processor. 32. The apparatus of claim 31, further comprising a plurality of caches, each of said caches connected to said shared memory and to one of said processors for reducing the need for said processor to access said shared memory. 33. The apparatus of claim 31, further comprising means for moving at least one of the movable threads from its unlocked local queue to said global dispatch queue. 34. The apparatus of claim 31, further comprising a mutex associated with said global dispatch queue for preventing said global dispatch queue from being changed by more than one thread at a time. 35. The apparatus of claim 31, further comprising a plurality of load indicators, each of said load indicators associated with one of said processors for identifying a busiest processor among said plurality of processors. 36. The apparatus of claim 35, wherein each of said load indicators indicates the number of sleep requests received by said associated processor during a sampling period. 37. The apparatus of claim 31, further comprising means for identifying a popular processor among the plurality of processors, a processor being popular when its unlocked local queue contains at least a predetermined number of eligible threads. -92- SUBSTITUTE SK_π RULE 26 38. The apparatus of claim 31, further comprising on each of said processors a means for searching in a predetermined order at least a portion of said global dispatch queue and said unlocked local queue of said processor to locate an eligible thread. 39. The apparatus of claim 38, further comprising a means for limiting the frequency of access to said global dispatch queue by said searching means. 40. The apparatus of claim 39, wherein said means for limiting the frequency of access comprises an access counter, means for initializing said access counter, means for decrementing said -access counter, and means for testing the value of said access counter. 41. The apparatus of claim 31, further comprising a plurality of lockable local queues of threads, each of said lockable local queues of threads being associated with one of said processors. 42. The apparatus of claim 41, further comprising a means for searching in a predetermined order at least a portion of one of said lockable local queues, said global dispatch queue, and one of said unlocked local queues, to locate an eligible thread. 43. The apparatus of claim 42, wherein said means for searching checks said lockable local queue of said processor for an eligible thread, checks said global dispatch queue if no eligible thread is found in said lockable local queue, and checks said unlocked local queue if no eligible thread is found in said global dispatch queue. 44. The apparatus of claim 42, wherein said searching means determines whether checking the global dispatch queue will exceed a predetermined relative frequency for global dispatch queue accesses, and checks said global dispatch queue only if such checking will not exceed the predetermined relative frequency for global dispatch queue accesses and if no eligible thread is found in said lockable local queue. -93- SUBSΠTUTE SHEET (RUUE 26)

Inventors: MERKEY JEFFREY V, MERKEY, JEFFREY, V.
Applicants: NOVELL INC, NOVELL, INC.
Application year: 1996
Main classification: G06F9
IPC codes: G06F9