⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 threads.cpp

📁 基于DOS系统下的多任务类库。Class DOS Thread provides a framework for writing DOS applications
💻 CPP
📖 第 1 页 / 共 2 页
字号:
//--------------------------------------------------------------------------
//
//      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 ();
    }
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -