home *** CD-ROM | disk | FTP | other *** search
- //--------------------------------------------------------------------------
- //
- // THREADS.CPP: body of DOS multithreading library.
- // Copyright (c) J.English 1993.
- // Author's address: je@unix.brighton.ac.uk
- //
- // Permission is granted to use copy and distribute the
- // information contained in this file provided that this
- // copyright notice is retained intact and that any software
- // or other document incorporating this file or parts thereof
- // makes the source code for the library of which this file
- // is a part freely available.
- //
- //--------------------------------------------------------------------------
- //
- // Note: this library is highly DOS specific and hence non-portable.
- // It also involves the use of assembly language and interrupt
- // functions, so it is also very compiler-specific and will need
- // modification for use with compilers other than Borland C++ 3.0
- // or later.
- //
- // Revision history:
- // 1.0 March 1993 Initial coding
- //
- //--------------------------------------------------------------------------
-
- #include "threads.h"
- #include <dos.h>
-
- //--------------------------------------------------------------------------
- //
- // Assembler sequences.
- //
- #define DISABLE asm { pushf; cli; } // save and disable interrupts
- #define ENABLE asm { popf; } // restore interrupt state
- #define PUSHAD asm { db 0x66, 0x60; } // push extended registers
- #define POPAD asm { db 0x66, 0x61; } // pop extended registers
- #define PUSH_FS asm { db 0x0F, 0xA0; } // push FS register
- #define PUSH_GS asm { db 0x0F, 0xA8; } // push GS register
- #define POP_FS asm { db 0x0F, 0xA1; } // pop FS register
- #define POP_GS asm { db 0x0F, 0xA9; } // pop GS register
-
-
- //--------------------------------------------------------------------------
- //
- // Typedefs and constants.
- //
- typedef void interrupt (*Handler)(...); // interrupt handler type
- typedef volatile unsigned Register; // interrupt handler parameter type
-
- const unsigned MIN_STACK = 512; // minimum stack size
- const long DAY_LENGTH = 1573040L; // length of day in timer ticks
-
-
- //--------------------------------------------------------------------------
- //
- // Global (static) variables.
- //
- static DOSThreadManager* current = 0; // current thread
- static DOSThreadManager* ready = 0; // queue of ready threads
- static DOSThreadManager* delayed = 0; // queue of delayed threads
- static DOSThread* mainthread = 0; // thread for execution of "main"
-
- static unsigned mainsetup = 0; // flag set when constructing "main"
- static unsigned slicesize = 1; // length of timeslice in clock ticks
- static unsigned i386; // flag set if executing on 386/486
-
- static volatile unsigned threadcount = 0; // number of active threads
- static volatile unsigned breakflag = 0; // flag set by control-break
- static volatile long nextslice = 0; // tick count for next timeslice
-
- static volatile long far* currtime = (volatile long far*) MK_FP(0x40,0x6C);
- static volatile char far* midnight = (volatile char far*) MK_FP(0x40,0x70);
- // timer values in BIOS data area
-
- //--------------------------------------------------------------------------
- //
- // Interrupt function pointers.
- //
- static Handler old_timer; // original timer interrupt handler
- static Handler old_dos; // original DOS services handler
- static Handler old_break; // original control-break handler
- static Handler old_error; // original critical error handler
-
- //--------------------------------------------------------------------------
- //
- // Identify CPU.
- //
- // This function is needed so that on 386/486 processors the
- // extended registers can be saved as part of a thread's context.
- // This is necessary in case the thread is interrupted during a
- // routine that relies on these registers being preserved. On
- // the 386 and above, bits 12 - 14 of the flag register can be
- // written to; on earlier processors, they are either always 1
- // (8086) or always 0 (286). The global variable "i386" is set
- // to 1 if we are executing on a 386 or above, and 0 otherwise.
- //
- void interrupt cputype ()
- {
- //--- Assume a 386 or above to start with
- i386 = 1;
-
- //--- Test for an 8086 (bits 12 - 15 of flags register always 1)
- _FLAGS = 0;
- if ((_FLAGS & 0xF000) == 0xF000)
- i386 = 0;
-
- //--- Test for a 286 (bits 12 - 14 of flags register always 0)
- _FLAGS = 0x7000;
- if ((_FLAGS & 0x7000) == 0)
- i386 = 0;
- }
-
- //--------------------------------------------------------------------------
- //
- // Class DOSNullThread.
- //
- // A concrete derivation of DOSThread used for the null thread and
- // the main thread. The main body just sits in an infinite loop.
- // A minimal stack is allocated for the purpose.
- //
- class DOSNullThread : public DOSThread
- {
- public:
- DOSNullThread () : DOSThread (MIN_STACK) { }
-
- protected:
- virtual void main ();
- };
-
- void DOSNullThread::main ()
- {
- for (;;) ; // do nothing
- }
-
-
- //--------------------------------------------------------------------------
- //
- // Class DOSCallMonitor.
- //
- // This class is a monitor which protects against re-entrant DOS
- // calls. It contains the DOS interrupt handler which uses "lock"
- // and "unlock" to prevent DOS being re-entered. Since interrupt
- // routines must be static and thus have no "this" pointer, there
- // is a single instance of this class declared which the interrupt
- // handler can use when calling "lock" and "unlock".
- //
- class DOSCallMonitor : public DOSMonitor
- {
- public:
- static void interrupt dos_int // handle DOS service calls
- (Register, Register, Register, Register,
- Register, Register, Register, Register,
- Register, Register, Register, Register);
- };
-
- static DOSCallMonitor dos; // instance used by DOS handler
-
- //--------------------------------------------------------------------------
- //
- // Class DOSThreadManager.
- //
- // This is a support class used for maintaining queues of threads.
- // A queue is represented as a circular list of DOSThreadManagers.
- // Each queue is headed by a DOSThreadManager with a null "thread"
- // pointer, so that queues and threads can be treated in a unified
- // manner -- it guarantees that queues will never be empty, and
- // simplifies moving threads around. The only exception to this
- // is the header for the ready queue, which has a pointer to the
- // null thread instead of a null pointer. This guarantees that the
- // ready queue always contains a runnable thread and that the null
- // thread will only ever be executed when the ready queue contains
- // no other runnable threads. The static member functions are also
- // included in this class so they can access the private parts of
- // the threads being controlled.
- //
- class DOSThreadManager
- {
- public:
- DOSThreadManager (DOSThread* t = 0) : thread (t), critflag (0)
- { next = prev = this; }
- void move (DOSThreadManager* t,
- DOSThread::State s); // move to position before "t"
-
- DOSThreadManager* next; // next entry in list
- DOSThreadManager* prev; // previous entry in list
- DOSThread* thread; // thread for this entry
- unsigned far* stkptr; // stack pointer
- unsigned long wakeup; // wakeup time in clock ticks
- unsigned critflag; // flag set during critical errors
-
- static void far start (DOSThread* t) // execute thread and then shut down
- { t->main(); t->terminate(); }
-
- static void cleanup (); // restore interrupt vectors at exit
- static void create (); // create main & null threads
- static void interrupt destroy (); // destroy main & null threads
- static void interrupt schedule (); // schedule next thread
- static void interrupt timer_int (); // handle timer interrupts
- static void interrupt break_int (); // handle control-break
- static void interrupt error_int // handle critical errors
- (Register, Register, Register,
- Register, Register, Register,
- Register, Register, Register);
- };
-
- //--------------------------------------------------------------------------
- //
- // Timer interrupt handler.
- //
- void interrupt DOSThreadManager::timer_int ()
- {
- //--- call old timer handler
- old_timer ();
-
- //--- move current thread to back of ready queue at end of timeslice
- long now = *currtime;
- if (nextslice >= DAY_LENGTH && *midnight != 0)
- nextslice -= DAY_LENGTH;
- if (slicesize > 0 && now >= nextslice && current == ready->next)
- current->move (ready, DOSThread::READY);
-
- //--- make threads with expired delays runnable
- DOSThreadManager* t = delayed->next;
- DOSThreadManager* r = ready->next;
- while (t != delayed)
- { if (t->wakeup >= DAY_LENGTH && *midnight != 0)
- t->wakeup -= DAY_LENGTH;
- if (now < t->wakeup)
- break;
- t = t->next;
- t->prev->move (r, DOSThread::READY);
- }
-
- //--- reschedule
- DOSThreadManager::schedule ();
- }
-
-
- //--------------------------------------------------------------------------
- //
- // Control-break interrupt handler.
- //
- // This routine just sets a flag for polling by individual threads.
- //
- void interrupt DOSThreadManager::break_int ()
- {
- breakflag = 1;
- }
-
-
- //--------------------------------------------------------------------------
- //
- // Critical error interrupt handler.
- //
- // This calls the critical error handler for the current thread
- // (which will always be the current one, since only one thread
- // can be executing a DOS call at any one time). "Critflag"
- // is set to inform the DOS interrupt handler that a critical
- // error is being handled.
- //
- void interrupt DOSThreadManager::error_int
- (Register, Register di, Register,
- Register, Register, Register,
- Register, Register, Register ax)
- {
- current->critflag = 1;
- ax = current->thread->DOSerror ((ax & 0xFF00) | (di & 0x00FF)) & 0xFF;
- current->critflag = 0;
- }
-
- //--------------------------------------------------------------------------
- //
- // DOS service interrupt handler.
- //
- // Since DOS is not re-entrant, this handler is required to protect
- // against threads making DOS calls while there is already one in
- // progress. It uses the monitor "dos" to prevent re-entrancy.
- // However, if a critical error occurs, the thread's critical error
- // handler may make a DOS call (but only functions 00 to 0C). In
- // this case, the lock operation is bypassed (the thread must already
- // be in a DOS function) but the function code is checked.
- //
- void interrupt DOSCallMonitor::dos_int
- (Register, Register di, Register si, Register,
- Register es, Register dx, Register cx, Register bx,
- Register ax, Register, Register, Register flags)
- {
- if (current->critflag == 0)
- dos.lock (); // prevent reentrance to DOS
- else if (ax > 0x0C)
- return; // critical error, functions > 0x0C not allowed
-
- //--- load registers from stacked values
- _FLAGS = flags;
- _DI = di;
- _SI = si;
- _ES = es;
- _DX = dx;
- _CX = cx;
- _BX = bx;
- _AX = ax;
-
- //--- call old DOS handler
- old_dos ();
-
- //--- store registers in stacked copies
- ax = _AX;
- bx = _BX;
- cx = _CX;
- dx = _DX;
- es = _ES;
- si = _SI;
- di = _DI;
- flags = _FLAGS;
-
- if (current->critflag == 0)
- dos.unlock (); //--- allow other threads in
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSThreadManager::cleanup.
- //
- // This is called by the thread manager destructor when the last
- // thread is destroyed, or by "atexit" if a quick exit happens. It
- // just restores the original interrupt vectors, so if it is called
- // multiple times during exit it won't matter.
- //
- void DOSThreadManager::cleanup ()
- {
- //--- unhook DOS vector by hand (can't use a DOS call to do it!)
- DISABLE;
- Handler far* dos = (Handler far*) MK_FP(0, 0x21*4);
- *dos = old_dos;
- ENABLE;
-
- //--- unhook other vectors
- setvect (0x08, old_timer);
- setvect (0x23, old_break);
- setvect (0x24, old_error);
- }
-
-
- //--------------------------------------------------------------------------
- //
- // DOSThreadManager::move.
- //
- // Move the caller's queue entry from its current position to the
- // position before entry "t". This must NEVER be used to move an
- // entry which marks the head of a queue! If "t" is a null pointer,
- // the entry is just unlinked from its current list and left hanging.
- // If the current thread is affected, a new thread is scheduled.
- //
- void DOSThreadManager::move (DOSThreadManager* t, DOSThread::State s)
- {
- DISABLE;
-
- //--- change thread status
- thread->state = s;
-
- //--- detach thread from current queue
- next->prev = prev;
- prev->next = next;
-
- //--- attach before specified position (if any)
- if (t != 0)
- { next = t;
- prev = t->prev;
- t->prev->next = this;
- t->prev = this;
- }
-
- ENABLE;
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSThreadManager::create.
- //
- // Register the creation of a thread by incrementing the number of
- // threads, and initialise the system if it is the first thread to
- // be created.
- //
- void DOSThreadManager::create ()
- {
- if (threadcount++ == 0)
- {
- //--- set "i386" if the processor being used is a 386 or above
- cputype ();
-
- //--- create the delay queue
- delayed = new DOSThreadManager;
-
- //--- create the ready queue and the null thread
- ready = (new DOSNullThread)->entry;
- ready->move (ready, DOSThread::READY);
-
- //--- create the main thread (heavily bodged with "mainsetup")
- mainsetup = 1;
- mainthread = new DOSNullThread;
- mainsetup = 0;
- mainthread->entry->move (ready, DOSThread::READY);
-
- //--- save interrupt vectors
- old_timer = getvect (0x08);
- old_dos = getvect (0x21);
- old_break = getvect (0x23);
- old_error = getvect (0x24);
- atexit (cleanup); // take care of sudden exits
-
- //--- hook interrupts (DOS interrupt last!)
- setvect (0x24, Handler (error_int));
- setvect (0x23, Handler (break_int));
- setvect (0x08, Handler (timer_int));
- setvect (0x21, Handler (DOSCallMonitor::dos_int));
- // DOS calls unsafe now
-
- //--- set the thread count and the current thread
- threadcount = 1;
- current = mainthread->entry; // DOS calls safe again
- DOSThreadManager::schedule ();
- }
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSThreadManager::destroy.
- //
- // This registers the destruction of a thread by decrementing the
- // number of threads, and shuts down the threading mechanism if the
- // last thread is being destroyed.
- //
- void interrupt DOSThreadManager::destroy ()
- {
- if (--threadcount == 0)
- {
- //--- unhook interrupt vectors
- cleanup ();
-
- //--- terminate main & null threads
- current = 0;
- ready->thread->state = DOSThread::TERMINATED;
- mainthread->state = DOSThread::TERMINATED;
-
- //--- delete main & null threads and the delay queue
- delete mainthread;
- delete ready->thread;
- delete delayed;
- }
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSThreadManager::schedule.
- //
- // Save the current thread and restore another one. Do nothing
- // if there is no current thread, or if the one to be scheduled
- // is already the current thread.
- //
- void interrupt DOSThreadManager::schedule ()
- {
- //--- disable interrupts (original state will be restored on exit)
- asm { cli; }
-
- //--- on a 386 or above, save the extended registers
- if (i386)
- { PUSH_FS;
- PUSH_GS;
- PUSHAD;
- }
-
- //--- switch threads if necessary
- if (current != ready->next && current != 0)
- {
- //--- set time for end of timeslice
- nextslice = *currtime + slicesize;
-
- //--- save current thread's stack pointer
- current->stkptr = (unsigned far*) MK_FP(_SS,_SP);
-
- //--- select new current thread
- current = ready->next;
-
- //--- restore its stack (other registers will be restored on exit)
- _SS = FP_SEG (current->stkptr);
- _SP = FP_OFF (current->stkptr);
- }
-
- //--- on a 386 or above, restore the extended registers
- if (i386)
- { POPAD;
- POP_GS;
- POP_FS;
- }
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::DOSThread.
- //
- // Construct a new thread. All new threads are kept terminated
- // until it is certain they can be started, and are then left in
- // limbo until they are explicitly started using "run".
- //
- DOSThread::DOSThread (unsigned stacksize)
- : stack (mainsetup ? 0 :
- new char [stacksize > MIN_STACK ? stacksize : MIN_STACK]),
- entry (new DOSThreadManager (this)),
- state (TERMINATED)
- {
- //--- leave thread terminated if any allocation failures have occurred
- if (!mainsetup && stack == 0)
- return; // stack not allocated
- if (entry == 0)
- return; // thread queue entry not allocated
-
- //--- register thread creation
- DOSThreadManager::create ();
-
- //--- initialise new thread (for all but main thread)
- if (!mainsetup)
- {
- //--- set up stack pointer
- entry->stkptr = (unsigned*)(stack + stacksize);
-
- //--- create initial stack
- asm { sti; } // ensure interrupts enabled!
- *--(DOSThread**)(entry->stkptr) = this; // parameter for "start"
- entry->stkptr -= 2; // dummy return address
- *--(entry->stkptr) = _FLAGS; // flags
- *--(entry->stkptr) = FP_SEG (&DOSThreadManager::start); // cs
- *--(entry->stkptr) = FP_OFF (&DOSThreadManager::start); // ip
- entry->stkptr -= 5; // ax, bx, cx, dx, es
- *--(entry->stkptr) = _DS; // ds
- entry->stkptr -= 2; // si, di
- *--(entry->stkptr) = _BP; // bp
-
- //--- stack extended registers on a 386 or above
- if (i386)
- entry->stkptr -= 18; // 8 x 32-bit regs, fs, gs
- }
-
- //--- allow thread to live (but don't move it into any queue)
- state = CREATED;
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::~DOSThread.
- //
- // Wait for current thread to terminate, and then destroy the evidence.
- //
- DOSThread::~DOSThread ()
- {
- //--- wait for thread to terminate (normal threads only)
- if (this != mainthread && this != ready->thread)
- wait ();
-
- //--- delete associated structures
- delete entry;
- delete stack;
-
- //--- register thread destruction (normal threads only)
- if (this != mainthread && this != ready->thread)
- DOSThreadManager::destroy ();
- }
-
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::wait.
- //
- // Wait for thread to terminate. This is needed to allow destructors
- // to avoid destroying threads while they are still running.
- //
- void DOSThread::wait ()
- {
- //--- make sure a thread is not trying to wait on itself
- if (this == current->thread)
- return;
-
- //--- terminate task if it hasn't started yet
- if (state == CREATED)
- state = TERMINATED;
-
- //--- wait for thread to terminate
- while (state != TERMINATED)
- pause ();
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::userbreak.
- //
- // This function returns the value of the flag which indicates if
- // control-break has been pressed.
- //
- int DOSThread::userbreak ()
- {
- return breakflag;
- }
-
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::cancelbreak.
- //
- // This function resets the flag which indicates if control-break has
- // been pressed. It also returns the original value of the flag.
- //
- int DOSThread::cancelbreak ()
- {
- DISABLE;
- int b = breakflag;
- breakflag = 0;
- ENABLE;
-
- return b;
- }
-
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::run.
- //
- // Start a new thread running.
- //
- int DOSThread::run ()
- {
- //--- error if thread is not newly created
- if (state != CREATED)
- return 0;
-
- //--- make thread ready to run and start it running
- entry->move (ready->next, READY);
- DOSThreadManager::schedule ();
- return 1;
- }
-
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::terminate.
- //
- // Immediately terminate a thread. The thread is detached from its
- // current queue ready to be destroyed.
- //
- void DOSThread::terminate ()
- {
- entry->move (0, TERMINATED);
- DOSThreadManager::schedule ();
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::delay.
- //
- // Delay for "n" clock ticks. The current thread is moved to the
- // correct position in the "delayed" queue and will be woken up
- // by the timer interrupt handler.
- //
- void DOSThread::delay (int n)
- {
- //--- don't delay if no current thread, or if tick count is non-positive
- if (current == 0 || n <= 0)
- return;
-
- //--- set wake-up time
- current->wakeup = *currtime + n;
-
- //--- find correct position in delay queue
- DOSThreadManager* t = delayed->next;
- while (t != delayed && t->wakeup < current->wakeup)
- t = t->next;
-
- //--- put thread in delay queue and reschedule
- current->move (t, DELAYED);
- DOSThreadManager::schedule ();
- }
-
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::pause.
- //
- // Move the current thread to the back of the ready queue.
- //
- void DOSThread::pause ()
- {
- //--- don't pause if no current thread
- if (current == 0)
- return;
-
- //--- move current thread to back of ready queue and reschedule
- current->move (ready, READY);
- DOSThreadManager::schedule ();
- }
-
-
- //--------------------------------------------------------------------------
- //
- // DOSThread::timeslice.
- //
- // Set the timeslice. This is ignored once the first thread has
- // been created.
- //
- void DOSThread::timeslice (unsigned n)
- {
- if (threadcount == 0)
- slicesize = n;
- }
-
- //--------------------------------------------------------------------------
- //
- // Constructors and destructors for DOSMonitorQueue and DOSMonitor.
- //
- // These are trivial but involve knowledge of DOSThreadManager, and
- // so cannot go in the header file.
- //
- DOSMonitorQueue::DOSMonitorQueue () : queue (new DOSThreadManager) { }
- DOSMonitorQueue::~DOSMonitorQueue () { delete queue; }
- DOSMonitor::DOSMonitor () : lockq (new DOSThreadManager),
- lockholder (0) { }
- DOSMonitor::~DOSMonitor () { delete lockq; }
-
-
- //--------------------------------------------------------------------------
- //
- // DOSMonitor::lock.
- //
- // This ensures that only one thread at a time is executing in a
- // monitor object, and must be called before any other monitor
- // functions (suspend, resume or unlock) can be used. If another
- // (non-terminated) thread already holds the lock, the current
- // thread is suspended by being placed at the back of the monitor
- // lock queue. When the current lock holder calls "unlock", all
- // queued threads are made ready, and the thread can then attempt
- // to acquire the lock again. The current thread must not be the
- // current lock holder (i.e. no recursive monitor calls are allowed).
- //
- void DOSMonitor::lock ()
- {
- //--- check for errors
- if (lockq == 0)
- error (NEW_FAIL); // lock queue doesn't exist
- if (current == 0)
- error (NO_THREAD); // no current thread
- if (lockholder == current)
- error (LOCK_FAIL); // current thread already holds lock
-
- DISABLE;
-
- //--- suspend repeatedly until lock is available
- while (lockholder != 0 &&
- lockholder->thread->status() != DOSThread::TERMINATED)
- { current->move (lockq, DOSThread::WAITING);
- DOSThreadManager::schedule ();
- }
-
- //--- make current thread the new lock holder
- lockholder = current;
-
- ENABLE;
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSMonitor::unlock.
- //
- // The calling thread must be the current lock holder. The lock is
- // released, and all threads requesting a lock are moved to the front
- // of the ready queue.
- //
- void DOSMonitor::unlock ()
- {
- //--- check for errors
- if (lockholder != current)
- error (UNLOCK_FAIL); // current thread isn't the lock holder
-
- DISABLE;
-
- //--- release lock
- lockholder = 0;
-
- //--- make pending threads ready to run
- DOSThreadManager* t = ready->next;
- while (lockq->next != lockq)
- lockq->next->move (t, DOSThread::READY);
-
- //--- reschedule
- DOSThreadManager::schedule ();
-
- ENABLE;
- }
-
- //--------------------------------------------------------------------------
- //
- // DOSMonitor::suspend.
- //
- // This function allows a monitor to suspend the current thread on
- // a monitor queue until another thread resumes it. The current
- // thread must be the current lock holder for the monitor. The
- // lock is released while the thread is suspended.
- //
- void DOSMonitor::suspend (DOSMonitorQueue& q)
- {
- //--- check for errors
- if (q.queue == 0)
- error (NEW_FAIL); // monitor queue doesn't exist
- if (lockholder != current)
- error (SUSPEND_FAIL); // current thread isn't the lock holder
-
- //--- release lock
- unlock ();
-
- //--- suspend current thread and reschedule
- current->move (q.queue, DOSThread::SUSPENDED);
- DOSThreadManager::schedule ();
-
- //--- lock the monitor again
- lock ();
- }
-
-
- //--------------------------------------------------------------------------
- //
- // DOSMonitor::resume.
- //
- // This function allows a monitor to resume any threads suspended
- // on a monitor queue. The current thread must be the current lock
- // holder for the monitor.
- //
- void DOSMonitor::resume (DOSMonitorQueue& q)
- {
- //--- check for errors
- if (q.queue == 0)
- error (NEW_FAIL); // monitor queue doesn't exist
- if (lockholder != current)
- error (RESUME_FAIL); // current thread isn't the lock holder
-
- //--- make any suspended threads wait for the lock to be released
- DOSThreadManager* lq = lockq->next;
- while (q.queue != q.queue->next)
- q.queue->next->move (lq, DOSThread::WAITING);
- }
-