📄 threads.cpp
字号:
//--------------------------------------------------------------------------
//
// 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);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -