?? threads.cpp
字號(hào):
//--------------------------------------------------------------------------
//
// 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 ();
}
}
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -