Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Parallel

Multiuser DOS for Control Systems Part I


APR92: MULTIUSER DOS FOR CONTROL SYSTEMS PART I

Richard is a senior engineer at Rockwell International Graphic Systems. He has a BSEE from the University of Illinois in Chicago and a MSCS from Illinois Institute of Technology. He can be contacted at 9616 South 49th Avenue, Oak Lawn, IL 60453.


The introduction of several versions of industrialized PC computers has made it possible to easily integrate desktop technology into the factory environment. While the PC is an excellent platform for program development, conventional DOS cannot provide the multitasking features required for development of control systems. Several alternative multitasking operating systems are available for PCs, including MTOS, QNX, and many flavors of UNIX, but none are directly compatible with DOS. Multiuser DOS (DRMDOS) from Digital Research, however, is a multitasking operating system that attempts to provide complete compatibility with DOS.

DRMDOS is a good platform for a control system for a number of reasons. Factory automation systems, for instance, are beginning to make increasing use of the power of graphics to convey information to the users. Using user interface libraries allows graphical front ends to be developed in a fraction of the time this work took previously. Therefore, DOS compatibility is important. Additionally, the multitasking features can be used to divide tasks into easily manageable chunks. Finally, with DRMDOS many programmers who don't have a great deal of experience with multitasking operating systems can develop sections of code that either don't require multitasking features or have the multitasking features disguised as function calls to mask complexity.

A project I recently worked on used DRMDOS as the operating system. I worked with a couple of other programmers who didn't have an extensive background in multitasking operating systems but did have DOS experience. During the project, I developed a library of functions to allow the use of DRMDOS's multitasking and interprocess communication features. This library can be used to turn normal DOS programs into efficient DRMDOS programs.

Even though DRMDOS is a preemptive multitasking operating system, the time slice allotted to each process is quite long. The system I used had a slice of 16.67 ms. A normal DOS-type program will hold on to the CPU for the full time slice. For running word processors and spreadsheets this is fine, but a control system can't tolerate this. We therefore needed a method of cutting the time a process holds on to the CPU to the minimum required to complete its task. The answer lies in cooperative multitasking, in which a process hangs on to the processor only long enough to perform one program loop or a specific set of functions. It then gives up the CPU to allow another process to run.

A programmer who works only on a single-tasking operating system often has difficulty breaking up a program into processes to be run on a multitasking operating system. The system I worked on used a shared-memory database to allow passing of information. In this case, any operation linked to another operation only by the database can be a separate process. In a control system, examples of separate processes are the operator interface, the I/O scanner, and serial communication routines.

Interfacing with DRMDOS

Before starting on this control system, I'd never ventured beyond the capabilities standard library function calls provide. To make use of DRMDOS's multitasking and interprocess communications features, it's necessary to use the int86 and int86x calls. These are the software interrupts that provide a low-level interface to DRMDOS as well as direct access to the BIOS functions; see Figure 1.

Figure 1: Interrupts that provide a low-level interface to DR Multiuser DOS

  int86(int_number, union REGS inregs,
                    union REGS outregs);

  int86x(int_number, union REGS inregs,
                     union REGS outregs,
                     struct SREGS seg_regs);

To access DRMDOS, the interrupt number (int_number) must be set to 224. The final part of the function calls may look a little strange. To use the int86 calls, a pair of unions must be declared. These unions are used to pass register information to and from the int86 functions. The union allows the corresponding word and byte representation of the CPU registers to be overlayed, as in Example 1(a). The inregs and outregs unions consist of the two structures in the example. The declarations for the union and the two structures are contained in the file dos.h, which is part of Microsoft QuickC; see Example 1(b).

Example 1: (a) Declaring paired unions; (b) declarations for the union and the two structures.

  (a)
  union REGS   { struct WORDREGS x;
                 struct BYTEREGS h;
               };

  (b)
  struct WORDREGS   { unsigned int ax;
                      unsigned int bx;
                      unsigned int cx;
                      unsigned int dx;
                      unsigned int si;
                      unsigned int di;
                      unsigned int cflag;
                    };

  struct BYTEREGS   { unsigned char al, ah;
                      unsigned char bl, bh;
                      unsigned char cl, ch;
                      unsigned char dl, dh;
                    };

Different representations of the registers are necessary because some function calls use the 8-bit registers (al, ah) and others use the 16-bit versions (ax). The (l) and (h) represent the low and high bytes of the register. For example, (al) is the low byte of the (a) register.

The difference between the int86 function calls is that the int86x call also passes the segment register information, some DRMDOS system calls require the segment registers to be passed. The segment register structure is also defined in the dos.h header file and is shown in Example 2.

Example 2: Segment register structure

  struct SREGS { unsigned int es;
                 unsigned int cs;
                 unsigned int ss;
                 unsigned int ds;
               };

Before either int86 function call is made, proper register values must be written to the inregs and segregs unions. The results of the function call will be written to the outregs union. These indicate the success or failure of the function call and are used to return other information, such as a pointer to a memory block.

DRMDOS Interface Library

DRMDOS provides a full range of services, from file operations to time and date functions. This section will show how to use the multitasking, shared memory, and queue system features of DRMDOS, which I used for an industrial control system. In the following discussion, the interrupt-number parameter is set to a defined value CCPM or 224 decimal. The library shown here does not include rigorous error checking. This would have to be added for completeness. In most cases, a returned value of 0 indicates that no errors occurred. The code for the interface library is contained in Listing One (page 110).

Multitasking Features

DRMDOS supports up to eight virtual consoles that are accessed using Alt-1 through Alt-8. This feature is useful for running up to eight programs written to run under DOS. However, this can be limiting in the case of control systems, which often have many more processes. These processes are frequently much smaller than a typical DOS program because they are designed to implement a specific part of a control system. If a program (or process) does not perform screen I/O, it can be run detached from the console, as a background process, by using the c_detach( ) function call.

The c_detach( ) call first declares the register unions and then assigns the proper value to the (cl) register. The int86 call is then made. The value in ax register is returned to indicate the status of the int86 call. As a result of using this call, the process becomes a background process.

Often, processes do not perform any operation that will cause them to give up the CPU. One such operation is a queue read or write. When such processes finish performing a task or program loop, they should allow the operating system scheduler to let another process run. This is done by using the p_dispatch( ) function call and causes the process to give up the CPU. The process is immediately put at the end of the list of processes ready to run.

The p_dispatch call first declares the register unions and then assigns the proper value to the (cl) register. The int86 call is then made, as a result of which the process is preempted. The process is then put back into the ready list to wait for its next chance to run.

In a control system, there are often functions that must be run at regular intervals. In the case of an industrial control system, digital I/O routines must be run at a definite interval so that important transitions are not missed. The p_priority( ) and p_delay( ) function calls accomplish this. The p_priority( ) call is used to change the priority of the function. In the DRMDOS operating system, priorities range from 0 to 255, 0 being the highest. Normal user programs in DRMDOS run at a priority of 200 decimal, the recommended range being 200 to 254; a priority of 255 is an idle process. Using priorities outside this range might affect operation of the operating system itself by causing necessary system processes to be delayed, and is therefore not recommended. Priority level 199 is listed as undefined, as are several other areas. The only problem with the I/O process being at a higher priority is that DRMDOS is a priority-based operating system. If nothing blocks it from running, it will hog the CPU. The p_delay( ) function call solves this problem: It causes the process to give up the CPU and be put onto a delayed process list. This essentially puts the process to sleep for a number of clock ticks. Once the process comes out of the delay mode, it will be the highest-priority user process, and will be run when the current process gives up the CPU or is forced to give it up due to a clock-tick interrupt.

The p_priority( ) call accepts a priority level passed in the function call. As stated earlier, levels from 0 to 255 are possible but not recommended. The p_priority call first declares the register unions and then loads the proper value into the (cl) register. The requested priority level is put into the (dl) register. Finally the int86 call is made. The process priority is immediately adjusted to the new value.

The p_delay( ) call accepts a requested delay time passed in the function call. The delay value can be in the range of 0 to 65,535 clock ticks. In my DRMDOS system, for example, the clock tick is 16.67 ms long, so to delay a process for about 50 ms, the number of clock ticks would be set to 3. The process declares the register unions and then loads the proper value to the (cl) register. The requested delay count is loaded into the (dx) register. Finally, the int86, call is made. The process is preempted and placed onto the waiting list. When the proper number of clock ticks has elapsed, the process will be returned to the ready list. A process is delayed at least the requested number of clock ticks.

System Memory

In many cases it is desirable to allow two or more processes to share a block of memory. I needed to support a memory-resident database shared among several processes. This database was to be accessed using a pointer to the memory and an offset to index into the database. To do this, you might simply declare an array for the database and pass a pointer using the queues, which will be covered below; unfortunately, this will not work. DRMDOS uses a banked memory system in which each program is overlayed on a so-called Transient Program Area (TPA). Thus the physical location of the database is not in the normal 640K range of DOS but somewhere in memory above 1 Mbyte. DRMDOS provides a way around this problem. The OS can be set to contain up to 9999 bytes of system memory. Because this is part of the operating system and is locked in the 640K range of memory, we can use a base pointer to access this memory.

The s_memory function call is provided to request a block of system memory. The function begins by declaring local variables, including the unions and structure. The value passed to the function is the number of words being requested. This value is doubled to get the number of bytes. The (cl) register is set to the system memory value and the (dx) register to the number of bytes. The int86x call is then made. If the (cx) register returns with a value of 0xFFFF, a NULL pointer is returned, meaning there was not sufficient memory available to be assigned. The segment of the pointer and the offset are returned in the (es) and (ax) registers, respectively. These are combined into a pointer and returned to the caller.

Version 5 of DRMDOS provides methods for requesting larger blocks of memory but I have not made use of this feature. These blocks reside in the extended memory area.

Interprocess Communications

Queues are a powerful method of passing data between two processes. The number of readers or writers that can use DRMDOS queues is not limited. In spite of this, I have found that typically, I use queues in two modes. The first is a data-collection mode--a queue, for instance, used to funnel messages to be sent out over a communication link. Many writers are potentially generating messages to be sent, but only one reader takes the messages and processes them. The second mode is the broadcast mode, used to distribute information from a single writer to many readers. One process creates the queue and writes the original information to it. Other processes read the information and then rewrite it to make it available to other processes. An example of this will be shown later when a process uses a queue to pass the pointer to a shared-memory database.

To be used, a queue must be created or made by a process. Once made, the queue must be opened. A pair of data structures--the queue descriptor and parameter blocks--must be used to make and open a queue (see Listing Two, page 111). The queue descriptor block tells DRMDOS some details of the queue. It contains several fields that must be filled in by the process and others used by DRMDOS to manage the queue. The queue parameter block is used to provide access to the queue.

The q_make( ) call allows the creation of queues. Five items are passed the q_make( ) function: a pointer to a queue descriptor block, the length and number of messages the queue will support, the name of the queue, and a pointer to an error variable. The error variable is used to return the status of the queue make operation. The function begins by declaring local variables, including the required unions and structure. The segment-register values are read and stored. Several queue descriptor fields are filled in with values required by DRMDOS. Next, the number and length of the messages are filled in. The flag's value is set to 0 to indicate that no flags are being used. The flags indicate the type of queue being created. A general-use queue has flags set to 0. A queue can also be declared strictly for semaphore or system use. The name of the queue is then copied into the proper field. Normally, a queue being created by a user process sets the buffer field to 0, meaning the operating system will use its own memory for the buffer. This leads to problems only if the system buffer space is used up. The DRMDOS programmer's guide gives more information about other methods of providing a buffer but I have not found it necessary to do this. The (cl) register is set to the q_make value. The (dx) register is set to the offset part of the pointer to the descriptor table using the FP_OFF call. The int86x call is then made. The error-pointer variable is set to the result code from the (cx) register. The result of the int86x call is also returned to provide additional information. If the call is successful, the queue is ready to be opened.

To access a queue, a process must first open it. Any process can open an existing queue using the q_open( ) function call. Three items are passed to the q_open function: a pointer to a queue parameter block, a queue name, and a pointer to an error variable. The function begins by declaring local variables, including the required unions and structure. The segment register values are read and stored. Two of the parameter block fields are set to 0, as required by DRMDOS. The filename is then copied into the parameter block. The (cl) register is set to the q_open value. The (dx) register is set to the offset part of the pointer to the parameter block using the FP_OFF( ) call. The int86x call is then made. The error-pointer variable is set to the result code from the (cx) register. The result of the int86x call is also returned to provide additional information. If the call is successful, the queue is ready to be written or read.

The q_read( ) and q_cread( ) function calls are used to read a message from the queue. The q_read( )function is an unconditional read: If no messages are available from the queue, the process is suspended until one is available. The q_cread( ) function is a conditional read: If no messages are available, the function returns a value to indicate this. Any nonzero value indicates an error condition.

The information passed to the functions include pointers to the parameter block, to a buffer used to receive the message, and to the error reporting location. The functions begin by declaring local variables, including the required unions and structure. The segment-register values are read and stored. The offset of the buffer is obtained using FP_OFF and put into the parameter block. The (cl) register is set to the proper value for the read or cread operation. The int86x call is then made. In the case of the conditional read, the error-reporting location is set to the value in the (cx) register. A value of 0 indicates that a message is available in the buffer. In the case of the unconditional read, the value will be nonzero if the queue does not exist or is not open. Upon a successful return, the message will be available for use. It is important to ensure that the buffer is sized properly to hold the message.

The q_write( ) and q_cwrite( ) calls are used to write a message to the queue. q_write( ) is an unconditional write: If there is no room in the queue to accept a message, the process is suspended until room is available. q_cwrite( ) is a conditional write: If no room is available in the buffer, the function will return a value to indicate this. Any nonzero value indicates an error condition.

The information passed to the functions includes pointers to the parameter block, to a buffer used to deliver the message, and to the error-reporting location. The functions begin by declaring local variables, including the required unions and structure. The segment-register values are read and stored. The offset of the buffer is obtained using the FP_OFF( ) call and put into the parameter block. The (cl) register is set to the proper value for the write or cwrite operation. The int86x call is then made. In the case of the conditional write, the error reporting location is set to the value in the (cx) register. A value of 0 indicates that a message is available in the buffer. In the case of the unconditional write, the value will be nonzero if the queue does not exist or is not open. Upon a successful return, the message will have been accepted by the queue and is available for reading.

Next Month

In next month's installment, I'll describe how the DRMDOS interface library can be used to develop a system consisting of three independent processes: The first is the owner of a memory resident database, the second an I/O process, and the third a logic function that monitors data in the input portion of the database for changes. When a change is encountered, the data is operated on to produce an output that is put back into the database.

Products Mentioned

DR Multiuser DOS Digital Research Inc. Box DRI Monterey, CA 93942 408-649-3896 $695 System requirements: 386SX, 386, or 486 PCs and compatibles



_MULTIUSER DOS FOR CONTROL SYSTEMS: PART I_
by Richard Kryszak


[LISTING ONE]
<a name="00ed_000f">

/* file name: system.c */

#include <dos.h>
#include "queues.h"
#include <stdio.h>

/*===============*/
/* local defines */
/*===============*/
#define CCPM        0xE0        /* cdos call int value */
#define C_DETACH    0x93        /* console detach CL register value */
#define P_DELAY     0x8D        /* process delay CL register value */
#define P_DISPATCH  0x8E        /* process dispatch CL register value */
#define P_PRIOR     0x91        /* process priority CL register value */
#define Q_CREAD     0x8A        /* queue cread CL register value */
#define Q_CWRITE    0x8C        /* queue cwrite CL register value */
#define Q_MAKE      0x86        /* queue make CL register value */
#define Q_OPEN      0x87        /* queue open CL register value */
#define Q_READ      0x89        /* queue read CL register value */
#define Q_WRITE     0x8B        /* queue write CL register value */
#define S_MEMORY    0x59        /* system memory allocation request */

/*=====================*/
/* function prototypes */
/*=====================*/
unsigned int c_detach(void);
void p_dispatch(void);
void p_priority(unsigned char data);
void p_delay(unsigned int del);
unsigned int far *  s_memory(int mem_size);
int q_make(struct q_descriptor *descript_ptr,
       unsigned int msg_length,
       unsigned int num_msg,
       char que_name[8],
       int *err_ptr);
int q_open(struct q_parameter_blk *param_blk_ptr,
       char que_name[8],
       int *err_ptr);
int q_read(struct q_parameter_blk *param_blk_ptr,
       unsigned char *buff_ptr,
       int *err_ptr);
int q_write(struct q_parameter_blk *param_blk_ptr,
        unsigned char *buff_ptr,
        int *err_ptr);
int q_cread(struct q_parameter_blk *param_blk_ptr,
        unsigned char *buff_ptr,
        int *err_ptr);
int q_cwrite(struct q_parameter_blk *param_blk_ptr,
         unsigned char *buff_ptr,
         int *err_ptr);

/*======================*/
/* function definitions */
/*======================*/
unsigned int c_detach()
   { union REGS inregs,outregs;

     inregs.h.cl = C_DETACH;                    /* detach function call */
     int86(CCPM,&inregs,&outregs);              /* call cdos */
     return(outregs.x.ax);                      /* return call status */
   }
void p_dispatch()
   { union  REGS inregs,outregs;

     inregs.h.cl = P_DISPATCH;                  /* dispatch function call */
     int86(CCPM,&inregs,&outregs);              /* call cdos */
   }
void p_priority(unsigned char priority)
   { union REGS inregs,outregs;

     inregs.h.cl = P_PRIOR;                     /* priority change call */
     inregs.h.dl = priority;                    /* desired priority */
     int86(CCPM,&inregs,&outregs);              /* call cdos */
   }
void p_delay(unsigned int del)
   { union  REGS inregs,outregs;
     inregs.h.cl = P_DELAY;                     /* delay function call */
     inregs.x.dx = del;                         /* number of ticks */
     int86(CCPM,&inregs,&outregs);              /* call cdos */
   }
unsigned int far * s_memory(int mem_size)
   { union  REGS inregs,outregs;
     struct SREGS seg_regs;                     /* segment registers */
     unsigned int _far *mem_ptr=NULL;           /* pointer to memory block */
     mem_size *= 2;                             /* compute # of bytes */
     inregs.h.cl = S_MEMORY;                    /* system memory allocation */
     inregs.x.dx = mem_size;                    /* # of bytes requested */
     int86x(CCPM,&inregs,&outregs,&seg_regs);   /* call cdos */
     if(outregs.x.ax == 0xFFFF)                 /* if not successful */
    { return(NULL);                         /* return a null pointer */
    }
     mem_ptr = (unsigned int far *)
            ((0x10000 * seg_regs.es)
             + outregs.x.ax);           /* convert into a pointer */
     return(mem_ptr);                           /* return the pointer */
   }
int q_make(struct q_descriptor *descript_ptr,
       unsigned int msg_length,
       unsigned int num_msg,
       char que_name[8],
       int *err_ptr)
   { int int86_error;                           /* return status */
     int i;                                     /* index variable */
     union REGS inregs, outregs;                /* processor registers */
     struct SREGS seg_regs;                     /* segment registers */
     segread(&seg_regs);                        /* read segment registers */
     descript_ptr->internal_1 = 0;              /* must be 0 */
     descript_ptr->internal_2 = 0;              /* must be 0 */
     descript_ptr->internal_3 = 0;              /* must be 0 */
     descript_ptr->internal_4 = 0;              /* must be 0 */
     descript_ptr->internal_5 = 0;              /* must be 0 */
     descript_ptr->internal_6 = 0;              /* must be 0 */
     descript_ptr->msglen = msg_length;         /* add message length */
     descript_ptr->nmsgs = num_msg;             /* add number of messages */
     descript_ptr->flags = 0;                   /* no flags used */
     for(i = 0; i < 8;  ++i)                    /* copy queue name */
    { descript_ptr->name[i]=que_name[i];
    }
     descript_ptr->buffer = 0;                  /* buffer in system area */
     inregs.h.cl = Q_MAKE;                      /* queue make call */
     inregs.x.dx = FP_OFF(descript_ptr);        /* put offset into dx */
     int86_error=int86x(CCPM,&inregs,
            &outregs,&seg_regs);    /* call cdos */
     *err_ptr = outregs.x.cx;                   /* write error code */
     return(int86_error);                       /* int86 return status */
   }
int q_open(struct q_parameter_blk *param_blk_ptr,
       char que_name[8],
       int *err_ptr)
   { int int86_error;                           /* return status */
     int i;                                     /* index variable */
     union REGS inregs, outregs;                /* processor registers */
     struct SREGS seg_regs;                     /* segment registers */
     segread(&seg_regs);                        /* read segment registers */
     param_blk_ptr->internal_1 = 0;             /* must be 0 */
     param_blk_ptr->internal_2 = 0;             /* must be 0 */
     for(i = 0; i < 8;  ++i)
    { param_blk_ptr->name[i] = que_name[i]; /* copy queue name */
    }
     inregs.h.cl = Q_OPEN;                      /* q_open call */
     inregs.x.dx = FP_OFF(param_blk_ptr);       /* put offset into dx */
     int86_error=int86x(CCPM,&inregs,
            &outregs,&seg_regs);    /* call cdos */
     *err_ptr = outregs.x.cx;                   /* write error code */
     return(int86_error);                       /* int86 return status */
   }
int q_write(struct q_parameter_blk *param_blk_ptr,
        unsigned char *buff_ptr,
        int *err_ptr)
   { int int86_error;                           /* return status */
     union REGS inregs, outregs;                /* processor registers */
     struct SREGS seg_regs;                     /* segment registers */
     segread(&seg_regs);                        /* read segment registers */
     param_blk_ptr->buffer=FP_OFF(buff_ptr);    /* pointer to the buffer */
     inregs.h.cl = Q_WRITE;                     /* q_write call */
     inregs.x.dx = FP_OFF(param_blk_ptr);       /* put offset into dx */
     int86_error=int86x(CCPM,&inregs, &outregs,&seg_regs);    /* call cdos */
     *err_ptr = outregs.x.cx;                   /* write error code */
     return(int86_error);                       /* int86 return status */
   }
int q_read(struct q_parameter_blk *param_blk_ptr,
       unsigned char *buff_ptr,
       int *err_ptr)
   { unsigned int int86_error;                  /* return status */
     union REGS inregs, outregs;                /* processor registers */
     struct SREGS seg_regs;                     /* segment registers */
     segread(&seg_regs);                        /* read segment registers */
     param_blk_ptr->buffer=FP_OFF(buff_ptr);    /* pointer to the buffer */
     inregs.h.cl = Q_READ;                      /* q_read call */
     inregs.x.dx = FP_OFF(param_blk_ptr);       /* put offset into dx */
     int86_error=int86x(CCPM,&inregs,&outregs,&seg_regs); /* int86 call */
     *err_ptr = outregs.x.cx;                   /* write error code */
     return(int86_error);                       /* int86 return status */
   }
int q_cwrite(struct q_parameter_blk *param_blk_ptr,unsigned char *buff_ptr,
                                                                 int *err_ptr)
   { int int86_error;                           /* return status */
     union REGS inregs, outregs;                /* processor registers */
     struct SREGS seg_regs;                     /* segment registers */
     segread(&seg_regs);                        /* read segment registers */
     param_blk_ptr->buffer=FP_OFF(buff_ptr);    /* pointer to the buffer */
     inregs.h.cl = Q_CWRITE;                    /* q_write call */
     inregs.x.dx = FP_OFF(param_blk_ptr);       /* put offset into dx */
     int86_error=int86x(CCPM,&inregs,&outregs,&seg_regs);    /* call cdos */
     *err_ptr = outregs.x.cx;                   /* write error code */
     return(int86_error);                       /* int86 return status */
   }
int q_cread(struct q_parameter_blk *param_blk_ptr,unsigned char *buff_ptr,
                                                        int *err_ptr)
   { int int86_error;                           /* return status */
     union REGS inregs, outregs;                /* processor registers */
     struct SREGS seg_regs;                     /* segment registers */
     segread(&seg_regs);                        /* read segment registers */
     param_blk_ptr->buffer=FP_OFF(buff_ptr);    /* pointer to the buffer */
     inregs.h.cl = Q_CREAD;                     /* q_cread call */
     inregs.x.dx = FP_OFF(param_blk_ptr);       /* put offset into dx */
     int86_error=int86x(CCPM,&inregs,&outregs,&seg_regs);    /* call cdos */
     *err_ptr = outregs.x.cx;                   /* write error */
     return(int86_error);                       /* int86 return status */
   }





<a name="00ed_0010">
<a name="00ed_0011">
[LISTING TWO]
<a name="00ed_0011">

/* file name: queues.h */

struct q_descriptor
{ unsigned int internal_1;      /* for internal use ; must be zero */
  unsigned int internal_2;      /* for internal use ; must be zero */
  int flags;                    /* for internal use ; queue flags */
  char name[8];                 /* queue name */
  int msglen;                   /* number of bytes in each logical message */
  int nmsgs;                    /* maximum number of messages supported */
  unsigned int  internal_3;     /* for internal use ; must be zero */
  unsigned int  internal_4;     /* for internal use ; must be zero */
  unsigned int  internal_5;     /* for internal use ; must be zero */
  unsigned int  internal_6;     /* for internal use ; must be zero */
  unsigned int  buffer;         /* address of the queue buffer */
 };

struct q_parameter_blk
{ unsigned int internal_1;      /* for internal use ; must be zero */
  int queueid;                  /* queue number field ; filled by q_open */
  unsigned int internal_2;      /* for internal use ; must be zero */
  unsigned int buffer;          /* offset of queue message buffer */
  char name[8];                 /* queue name */
 };





<a name="00ed_0012">
<a name="00ed_0013">
[LISTING THREE]
<a name="00ed_0013">

/* file name: database.c */

#include <stdio.h>
#include "queues.h"

/*=====================*/
/* function prototypes */
/*=====================*/
void main(void);

/*===============*/
/* local defines */
/*===============*/
#define Q_DEPTH         1               /* queue contains 1 message */
#define DBASE_SIZE      2048            /* size of the database */
#define TRUE            1

/*================================*/
/* external function declarations */
/*================================*/
extern unsigned int c_detach(void);
extern void p_delay(unsigned int del);
extern unsigned int far *s_memory(int mem_size);
extern int q_make(struct q_descriptor *descript_ptr,
          unsigned int msg_length,
          unsigned int num_msg,
          char que_name[8],
          int *err_ptr);
extern int q_open(struct q_parameter_blk *param_blk_ptr,
          char que_name[8],
          int *err_ptr);
extern int q_write(struct q_parameter_blk *param_blk_ptr,
           unsigned char *buff_ptr,
           int *err_ptr);
/*=====================*/
/* function definition */
/*=====================*/
void main()
   { int result;                                /* result of q_make */
     int error_type;                            /* cdos return code */
     union base
    { unsigned int far *base_ptr;
      unsigned char base[sizeof(unsigned int far *)];
    }base_union;                             /* composite pointer */
     struct q_descriptor dbase_descript;         /* descriptor block */
     struct q_parameter_blk dbase_parameters;    /* parameter block */
     base_union.base_ptr = s_memory(DBASE_SIZE); /* request system memory */
     if(base_union.base_ptr == NULL)             /* if NULL pointer */
    { puts("No System Memory Available");    /* print an error message */
      exit(-1);                              /* exit, memory error */
    }
     result = q_make(&dbase_descript,            /* pointer to descriptor */
             sizeof(base_union),         /* length of messages */
              Q_DEPTH,               /* number of messages */
              "database",            /* queue name */
              &error_type);          /* error return */
      result = q_open(&dbase_parameters,     /* pointer to parameter */
              "database",            /* queue name */
              &error_type);          /* error return */
      result = q_cwrite(&dbase_parameters,   /* write to queue */
                &base_union.base[0], /* pointer to database */
                &error_type);        /* error return */
      c_detach();                            /* detach from console */
      while(TRUE)                            /* loop */
        { p_delay(1800);                 /* delay 30 seconds */
        }
    }
/* NOTE: DATABASE.EXE is made up of database.c and system.c */





<a name="00ed_0014">
<a name="00ed_0015">
[LISTING FOUR]
<a name="00ed_0015">

/* file name: dbsuport.c */

#include <stdio.h>
#include "queues.h"

/*=====================*/
/* function prototypes */
/*=====================*/
void dbopen(void);
unsigned int dbread(int index);
void dbwrit(int index, unsigned int value);
unsigned int far *open_dbase(void);

/*==============================*/
/* external function prototypes */
/*==============================*/
extern int q_open(struct q_parameter_blk *param_blk_ptr,
       char que_name[8],
       int *err_ptr);
extern void p_dispatch(void);
extern int q_read(struct q_parameter_blk *param_blk_ptr,
          unsigned char *buff_ptr,
          int *err_ptr);
extern int q_write(struct q_parameter_blk *param_blk_ptr,
           unsigned char *buff_ptr,
           int *err_ptr);

/*================*/
/* global storage */
/*================*/
unsigned int far *dbase_ptr;                    /* pointer to database */

/*===============*/
/* local defines */
/*===============*/
#define FAILURE         1
#define SUCCESS         0

/*======================*/
/* function definitions */
/*======================*/
void dbopen()
   { dbase_ptr = NULL;                             /* initialize pointer */
     while(dbase_ptr == NULL)                   /* loop while still NULL */
    { dbase_ptr = open_dbase();             /* call open database */
    }
   }
void dbwrit(int index, unsigned int value)
    { *(dbase_ptr + index) = value;        /* write value to database */
    }
unsigned int dbread(int index)
    { return(*(dbase_ptr + index));        /* return value at index */
    }
unsigned int far *open_dbase()
   { struct q_parameter_blk dbase_parameters;   /* parameter block */
     int result;                                /* result of q_make */
     int error_type;                            /* cdos return code */
     union base
    { unsigned int far *base_ptr;
      unsigned char base[sizeof(unsigned int far *)];
    }base_union;                            /* composite pointer */
     result = FAILURE;                          /* preset the variable */
     while(result != SUCCESS)                   /* loop til we can open */
    { result = q_open(&dbase_parameters,    /* pointer to param block */
              "database",           /* queue name */
              &error_type);         /* error type return */
      p_dispatch();                         /* let someone else run */
    }
     result = q_read(&dbase_parameters,         /* read the dbase_queue */
             &base_union.base[0],       /* msg read is in union */
             &error_type);              /* error return type */
     result = q_write(&dbase_parameters,        /* write to dbase_queue */
              &base_union.base[0],      /* msg sent is pointer */
              &error_type);             /* error return type */
     return(base_union.base_ptr);               /* return the pointer */
   }






<a name="00ed_0016">
<a name="00ed_0017">
[LISTING FIVE]
<a name="00ed_0017">

/* file name ioboard.c */

#include <conio.h>

/*====================*/
/* function prototype */
/*====================*/
void main(void);

/*==============================*/
/* external function prototypes */
/*==============================*/
extern void dbopen(void);
extern void dbwrit(int index, unsigned int value);
extern unsigned int dbread(int index);
extern unsigned int c_detach(void);
extern void p_priority(unsigned char data);
extern void p_delay(unsigned int del);

/*===============*/
/* local defines */
/*===============*/
#define INPUT_BASE_ADDR  0x300                  /* hardware input address */
#define OUTPUT_BASE_ADDR 0x300                  /* hardware output address */
#define DBASE_WRITE_ADDR 0                      /* database write location */
#define DBASE_READ_ADDR  1                      /* database read location */
#define TRUE             1

/*=====================*/
/* function definition */
/*=====================*/
void main()
   { unsigned int temp_data;                    /* for reading database */
     p_priority(199);                           /* set priority */
     dbopen();                                  /* link to database */
     c_detach();                                /* detach from console */
     while(TRUE)
    { temp_data = inp(INPUT_BASE_ADDR);     /* read data from port */
      dbwrit(DBASE_WRITE_ADDR, temp_data);  /* write to the database */
      temp_data = dbread(DBASE_READ_ADDR);  /* read data from database */
      outp(OUTPUT_BASE_ADDR+3, temp_data);  /* write to output port */
      p_delay(3);                           /* delay for 50 ms */
    }
   }

/* NOTE: IOBOARD.EXE is made up of ioboard.c, system.c, and dbsuport.c */





<a name="00ed_0018">
<a name="00ed_0019">
[LISTING SIX]
<a name="00ed_0019">

/* file name logic.c */

/*====================*/
/* function prototype */
/*====================*/
void main(void);

/*==============================*/
/* external function prototypes */
/*==============================*/
extern void dbopen(void);
extern unsigned int dbread(int index);
extern void dbwrit(int index, unsigned int value);
extern unsigned int c_detach(void);
extern void p_dispatch(void);

/*===============*/
/* local defines */
/*===============*/
#define DATA_IN  0                      /* data written by I/O process */
#define DATA_OUT 1                      /* data read by I/O process */
#define TRUE     1

/*=====================*/
/* function definition */
/*=====================*/
void main()
   { static unsigned int last_data_read;        /* old data retainer */
     unsigned int temp_data;                    /* for reading database */

     dbopen();                                  /* link to database */
     c_detach();                                /* detach from console */
     while(TRUE)                                /* continuous loop */
    { temp_data = dbread(DATA_IN);          /* read input data */
      if(temp_data ^ last_data_read)        /* if there was a change */
         { dbwrit(DATA_OUT, ~temp_data);    /* write the to the port */
           last_data_read = temp_data;      /* save the new value */
         }
      p_dispatch();                         /* let another process run */
    }
   }
/* NOTE: LOGIC.EXE is made up of logic.c, system.c, and dbsuport.c */


Copyright © 1992, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.