The iRMX Family Of Operating Systems



January 01, 1991
URL:http://www.drdobbs.com/the-irmx-family-of-operating-systems/184402293

January 1991/The iRMX Family Of Operating Systems

Richard Carver is a software engineer with six years experience programming in C on iRMX systems. He received his B.S. in computer science from Akron University (Akron, Ohio). He has worked on projects involving financial transaction processing, security systems and medical diagnostics equipment. He may be reached on CompuServe (71061,1754) at the Real-Time Forum (GO REALTIME) or by phone at (708) 249-0633.

iRMX is a "layered real-time object-oriented multi-user multitasking interrupted-driven priority-based preemptive operating system." iRMX I, iRMX II and iRMX III are a family of operating systems designed for the Intel 80x86 family of microprocessors. iRMX I uses "Real Mode" addressing, which accesses up to 1 megabyte of system memory. iRMX II uses the "Protected Virtual Address Mode" (PVAM) of the 80286 and 80386/486 processors, which permits addressing up to 16 megabytes. In addition, the protected mode supports segment boundary protection, stack-overflow detection, invalid segment detection and segment access rights (read, write, or execute). With iRMX III, the 32-bit addressing of the 80386/486 allows accessing up to 4 gigabytes using PVAM.

The iRMX operating systems are supported for three major industry-standard 80x86-based bus architectures: Intel's MULTIBUS I & II and IBM's PC/AT architecture.

In almost all cases, applications written for a lower member of the OS family can be easily ported to a higher member (e.g., iRMX I to iRMX II). In fact, by using the features of the 80386/486 processor for supporting 16-bit and 32-bit code, almost all iRMX II applications (executable code) can be run directly under iRMX III.

Basics Of The iRMX Nucleus Layer

At the heart of the iRMX OS is the Nucleus Layer. This layer of the operating system supports scheduling, controls access to system resources, provides communication between processes and processors, and enables the system to respond to external events. It provides the basic "objects" for all other layers (Basic I/O, Extended I/O, Application Loader, and Human Interface) and applications. The objects provided by the Nucleus include: Tasks, Jobs, Segments, Mailboxes, Semaphores, Regions, Extension Objects, and Composite Objects. These objects may be created, destroyed and manipulated by programs. When an object is created, a token for the object is returned. This object token is used for all references to the object.

Within the iRMX OS, work is performed by tasks. The tasks operate within the environment provided by the job. This includes the tasks, objects used by the tasks, a directory for tasks to catalog objects and a memory pool. Every job has at least one task to refer to as the "initial" task. This task may create other tasks to accomplish the work of the program. Tasks may also create new jobs (child jobs) with their own environments. Creating new jobs results in a Job Tree with parent/child connections. Tasks creating other tasks do not result in parent/child relationships. Tasks within a single job are part of the job as a whole.

Tasks may communicate with each other using the iRMX "Exchange Objects." The three types of exchange objects are mailboxes, semaphores, and regions. Mailboxes can be either data mailboxes, used for sending and receiving data, or object mailboxes, which are used for sending and receiving objects. Mailboxes are created and deleted using the rqcreatemailbox and rqdeletemailbox system calls. Data mailboxes are used with the rqsenddata and rqreceivedata system calls to transfer data (up to 128 bytes) from one task to another. Data is physically copied from the sender to the receiver using the mailbox as a transfer buffer. Object mailboxes are used with the rqsendmessage and rqreceivemessage system call to pass object tokens between tasks. The object token may be for any valid iRMX object (e.g., tasks, jobs, segments, mailboxes, semaphores, regions).

Semaphores are the "keepers of units." A unit is an abstract object that can be sent to or received from a semaphore. Semaphores are often used for controlling access to shared system resources (e.g., data, devices) or for synchronizing tasks. iRMX semaphores can also be used as "counting" semaphores, which means that they are capable of holding more than a single unit. Semaphores are supported through the rqcreatesemaphore, rqdeletesemaphore, rqsendunits, and rqreceiveunits system calls.

Regions are special forms of semaphores. Tasks may receive or release control of regions. In this manner, regions work like "single unit" semaphores. However, when a task receives control of a region, its priority is increased to allow the task to run until it releases the region. Also, the task cannot be suspended or deleted until the region is released. Because of this, regions are only used in situations where semaphores do not provide enough control. Regions are supported through the rqcreateregion, rqdeleteregion, rqsendcontrol, and rqreceivecontrol system calls.

Sharing Through Tasks

You can accomplish access to shared resources, such as data or devices, by using semaphores. However, sometimes it makes more sense to implement resource control through tasks. For a given resource, such as a terminal, a task is written to provide the services of this resource. The task accepts requests from other tasks for operations to be performed, such as printing a message to the display.

The example program consists of five tasks. The first task is the initial task which it responsible for creating all other tasks and program termination. The clock and crt tasks act as resource managers for the clock and crt, respectively. The count and timer tasks are the resource consumers.

This program uses only semaphores and object mailboxes for communication. Regions are rarely used and are specifically not recommended for use in operator-executed programs. Data mailboxes are not used because they only provide for one-way communications. Typically, two-way communication, such as that provided by rqsendmessage and rqreceivemessage with object mailboxes, provides task synchronization.

The Initial Task

The initial task is the main() program. It creates many of the objects used during the program and catalogs these objects so that the other tasks may use them. This task then creates one task at a time, each time waiting for a task to indicate that it has completed initialization by sending a unit, using rqsendunit, to the initialization semaphore (init_sem). When the last task, timer_task(), signals its completion, the shutdown process begins. Each remaining task is sent a unit to its respective shutdown semaphores. When all tasks indicate they have shutdown, all objects are cleaned up (uncatalogued and deleted) and the program is terminated.

The Clock Task

The clock_task() provides access to the hardware clock and implements the software timers. All timers are maintained using the single hardware interrupt from the clock board. This task also maintains the display time/date by communicating with the crt_task().

The clock_task has been set-up to service an interrupt. It became an interrupt task when it called the rqsetinterrupt system call. This call specifies the interrupt to be serviced and the address of an interrupt handler. The handler is not the task, it is a function that receives control when the interrupt occurs. The interrupt handler passes control to the interrupt task by using rqsignal interrupt. The interrupt task is waiting to receive control through rqetimedinterrupt. The main difference between the handler and the task is that all interrupts are disabled when the handler is in control. Only the interrupt being serviced is disabled when the task in control. Also, interrupt handlers are restricted to interrupt-related system calls. Since this implementation needs to access semaphores and mailboxes, it must use an interrupt task.

When an interrupt occurs, the task first requests access to the shared time/date data segment. It then checks to see if the hardware clock needs to be changed (supported but not used in example). If no change is required, the current time/date is advanced by one second. Next, all software timers, if any, are advanced by one second. If a timer's timeout value is reached, a unit is sent to the timer semaphore and the timer restarts. Finally, the time/date is formatted and sent to the crt_task() for display. This task communication is synchronous, but the send and receive do not occur one after the other. A display message is not sent unless the response to the last request has been received. This prevents the task from being delayed during interrupt processing and prevents a build up of request messages when the crt_task() is busy (which could occur since the crt_task() has a lower priority). The minor side effect is that the time/date display may skip a second when then crt_task() is busy.

When the task receives the shutdown signal, it waits for the outstanding display response and calls rqresetinterrupt to disable itself as an interrupt processing task. It will then suspend itself to await deletion.

The Crt Task

The crt_task() provides shared access to the video display. This is a basic implementation, providing only a means of printing a string at a specific row and column of the display. The task receives request messages that indicate a message to be displayed and the location to display the message. After displaying the message, the task determines if a response is expected. If the calling task indicated a "response mailbox" in the rqsendmessage call, then it will be expecting a response. This facility is used for synchronizing the task communication. The response will be the original message segment. If the task did not expect a response, the "response mailbox" value will be the null token (NUL_TKN). In this case, this task will free the request message segment.

When the task receives the shutdown signal, it will go into an infinite loop of receiving requests and returning response messages (if a response is required). Since many tasks send a request and then wait for the response, you don't want tasks getting stuck waiting for responses. This could happen if the crt_task() quits responding after a shutdown signal. Requests messages will pile up in the request mailbox and will never be processed. Minimum processing to send the response is needed.

The Count Task

The count_task() continuously counts from 0-65535 and displays the value in the middle of the screen by communicating with the crt_task(). This task's purpose is to help demonstrate how multiple tasks can use the crt_task() to safely share the video display.

Upon receiving a unit at its shutdown semaphore, the task suspends itself to await deletion.

The Timer Task

The timer_task() uses a software timer, maintained by the clock_task(), to determine when the program should be terminated. The task creates a one-second timer and waits for the timer to be signalled 20 times (20 seconds). The timer is actually a semaphore that has been set up so that the clock_task() sends a unit to the semaphore every second. The timer_task() calls rqreceiveunits to wait for 20 units to be sent to the semaphore. (A single unit from a 20-second timer could also be used.) When 20 seconds have elapsed, the task sends a unit to the initialization semaphore (init_sem). In this case, receiving a unit at init_sem not only indicates that the task has initialized but that it is also time to terminate the program. Since it initiates the shutdown signal process, it suspends itself.

Summary

The clock_task() is a fairly complete task. There isn't much more useful functionality that would be added to service a hardware clock. On the other hand, the crt_task() is only the core of what could be a sophisticated display servicing task supporting many features (e.g., video attributes, hotkeys, windows).

Remember, the example program is just that — an example of task communications. It is by no means the only way to implement and organize tasks within an application program. Because multitasking operating systems are usually very complex, you should use good design and modular programming techniques. But don't let this scare you, once you become familiar with a multitasking operating system, such as iRMX, you'll wonder know how you ever got along without it.

Listing 1

Sidebar: "iRMX C Code For 80x86 Processors"

January 1991/The iRMX Family Of Operating Systems/Listing 1

Listing 1 (clockint.c)

/*0*********************************************************
*   Richard Carver                            July 1990
*
*   This program demonstrates interprocess communication
*   between several tasks using the iRMX II Operating
*   System.
*
*   Two tasks are used for resource control. The
*   clock_task() controls use of the hardware clock and the
*   crt_task() controls use of the display screen.
*
*   The count_task() competes with the clock_task() for
*   use of the crt_task() services. The timer_task() uses
*   the services of the clock_task() for a software timer.
*
*   The initial task, main(), starts every thing up and,
*   when the timer_task() finishes, shuts everything down.
***********************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <rmxc.h>       /* iRMX OS calls & structures  */
#include <rmxerr.h>     /* iRMX status codes (defines) */
#include <delay.h>      /* Hardcoded delay loop macros */

#define ERROR   (0xFFFF)   /* Error Condition */

#define FALSE   (0)
#define TRUE    (!FALSE)

#define MAX_CLOCK_RETRIES  (4)

/* The NULL token (similar to NULL pointer) */

#define NUL_TKN   ((selector)0)

/* Options for rqgettasktokens() */

#define THIS_TASK (0x00)
#define THIS_JOB  (0x01)
#define PARAM_OBJ (0x02)
#define ROOT_JOB  (0x03)

/* Options for rqcreatemailbox() */

#define FIFO_OBJ_MBX       (0x0000)
#define PRIORITY_OBJ_HBX   (0x0001)
#define FIFO_DATA_MBX      (0x0020)
#define PRIORITY_DATA_MBX  (0x0021)

/* Options for rqcreatesemaphore() */

#define FIFO_SEM      (0x0000)
#define PRIORITY_SEM  (0x0001)

#define SEM_INIT_0    (0)
#define SEM_MAX_1     (1)

/* Common wait times for iRMX System Calls */

#define NO_WAIT       (0x0000)
#define WAIT_FOREVER  (0xFFFF)

/* Options for rqcreatetask() */

#define NO_FLOAT   (0x0000)
#define DEFAULT_STACK_SIZE     ((unsigned int)2048)

/* Task Priorities  */

#define CLOCK_TASK_PRIORITY    ((unsigned char)48)
#define TIMER_TASK_PRIORITY        ((unsigned char)150)
#define CRT_TASK_PRIORITY      ((unsigned char)200)
#define COUNT_TASK_PRIORITY    ((unsigned char)200)

/* Defines For Hardware Clock */
/* Clock Interrupt Level */

#define CLOCK_INT_LEVEL (0x26)

/* Clock Ports */

#define ADR_PORT  ((unsigned short)0xA060)
#define DAT_PORT  ((unsigned short)0xA062)
#define CNT_PORT  ((unsigned short)0xA064)
#define PIO_PORT  ((unsigned short)0xA066)

/* Clock Port I/O Operations */

#define PIO_READ  ((unsigned char)0x82)
#define PIO_WRITE ((unsigned char)0x80)

/* Clock Registers */

#define SEC01_REG (0x00)
#define SEC10_REG (0x01)
#define MIN01_REG (0x02)
#define MIN10_REG (0x03)
#define HRS01_REG (0x04)
#define HRS10_REG (0x05)
#define DAT01_REG (0x07)
#define DAT10_REG (0x08)
#define MON01_REG (0x09)
#define MON10_REG (0x0A)
#define YRS01_REG (0x0B)
#define YRS10_REG (0x0C)

/* Clock Control Lines */

#define CNT_RST     ((unsigned char)0x00)
#define INT_ENB     ((unsigned char)0x20)
#define HOLD_HIGH   ((unsigned char)0x10)
#define HOLD_READ   ((unsigned char)0x30)
#define HOLD_WRITE  ((unsigned char)0x50)

/* Defines for months */

#define JANUARY     (1)
#define FEBRUARY    (2)
#define MARCH       (3)
#define DECEMBER    (12)

/* Time/Date and Timers Data Structure  */

#define MAX_TIMERS    (10)

struct SYS_TIME_STR
  {
  unsigned char change_flg;
  unsigned char second;
  unsigned char minute;
  unsigned char hour;
  unsigned char date;
  unsigned char month;
  unsigned char year;
  
  struct TIMER_STR
    {
    unsigned char in_use;
    unsigned int count;
    unsigned int timeout;
    selector    sem_tkn;
    }timer[MAX_TIMERS];
  };

/* Request Message format for the Display Task */

struct CRT_REQ_STR
  {
   unsigned char *msg_ptr;
   unsigned int  row;
   unsigned int  col;
   };
 
 /* Object Catalog Names */
 
 const unsigned char
   clock_sem_name[] = "\011CLOCK_SEM",
   init_sem_name[] = "\010INIT_SEM";
 
 const unsigned char
   time_seg_name[] = "\010TIME_SEG";
 
 const unsigned char
   crt_req_mbx_name[] = "\007CRT_MBX";
 
 const unsigned char
   crt_shtdwn_sem_name[] = "\012CRT_SHTDWN",
   clock_shtdwn_sem_name[] = "\014CLOCK_SHTDWN",
   count_shtdwn_sem_name[] = "\014COUNT_SHTDWN";
 
 /* Days Per Month */
 
 const unsigned char
   days_in_month[12] =
   {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/* Global System Time/Date Structure */

selector  sys_time_seg;
struct SYS_TIME_STR *sys_time;

/* Task declarations  */

void clock_task(void);
void crt_task(void);
void timer_task(void);
void count_task(void);

/* Task objects (global for debugging purposes) */

selector  clock_tsk;
selector  crt_tsk;
selector  timer_tsk;
selector  count_tsk;

/*1*********************************************************
* Task:     main()  (the initial task)
*
* Summary:  Starts it all up and shuts it all down.
*
* Caveats:  None
***********************************************************/

void main(void)
  {
  selector  current_job;       /* current job            */
  selector  init_sem;          /* initilaization sem     */
  selector  clock_sem;         /* time/date shared memory*/
  selector  clock_shtdwn_sem;  /* clock_task() shutdown  */
  selector  count_shtdwn_sem;  /* count_task() shutdown  */
  selector  crt_shtdwn_sem;    /* crt_task() shutdown    */
  selector  sys_time_seg;      /* time/date data segment */
  
  unsigned  int   status;      /* iRMX condition code    */
  
  struct EXCEPTIONSTRUCT exceptioninfo;

/*  Disable current task's exception handler  */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);
/*  Get Token For Current Job */

  current_job = rqgettasktokens(THIS_JOB, &status);

/*  Create and catalog the semaphore used for */
/*  accessing the shared clock data.          */


  clock_sem = rqcreatesemaphore(SEM_INIT_0,
    SEM_MAX_1, PRIORITY_SEM, &status);
  rqcatalogobject(current_job,
    clock_sem, &clock_sem_name, &status);

/*  Create and catalog the semaphore used for */
/*  synchronizing tasks during initialization */
/*  and shutdown.                             */

  init_sem = rqcreatesemaphore(SEM_INIT_0,
    99, FIFO_SEM, &status);
  rqcatalogobject(current_job,
    init_sem, &init_sem_name, &status);

/*  Create and catalog the segment of memory  */
/*  used to hold the shared time/date data.   */

  sys_time_seg = rqcreatesegment(
    sizeof(struct SYS_TIME_STR), &status);
  rqcatalogobject(current_job,
    sys_time_seg, &time_seg_name, &status);

/*  Create and catalog the semaphores used to */
/*  signal task shutdown.                     */

  clock_shtdwn_sem = rqcreatesemaphore(SEM_INIT_0,
    SEM_MAX_1, FIFO_SEM, &status);
  rqcatalogobject(current_job,
    clock_shtdwn_sem, &clock_shtdwn_sem_name, &status);

  crt_shtdwn_sem = rqcreatesemaphore(SEM_INIT_0,
    SEM_MAX_1, FIFO_SEM, &status);
  rqcatalogobject(current_job,
    crt_shtdwn_sem, &crt_shtdwn_sem_name, &status);

  count_shtdwn_sem = rqcreatesemaphore(SEM_INIT_0,
    SEM_MAX_1, FIFO_SEM, &status);
  rqcatalogobject(current_job,
    count_shtdwn_sem, &count_shtdwn_sem_name, &status);

/*  Create the crt_task(). Wait for the task to  */
/*  indicate it has completed initialization.    */

  crt_tsk = rqcreatetask(CRT_TASK_PRIORITY, &crt_task,
    NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
  
  rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);

/*  Create the clock_task(). Wait for the task    */
/*  to indicate it has completed initialization.  */

  clock_tsk = rqcreatetask(CLOCK_TASK_PRIORITY, &clock_task,
    NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
  
  rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);

/*  Create the count_task(). Wait for the task    */
/*  to indicate it has completed initialization.  */

  count_tsk = rqcreatetask(COUNT_TASK_PRIORITY, &count_task,
    NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
  
  rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);

/*  Create the timer_task(). Wait for the task    */
/*  to indicate it has completed initialization.  */
/*  This also signals the shutdown process.       */
  timer_tsk = rqcreatetask(TIMER_TASK_PRIORITY, &timer_task,
    NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
  
  rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);

/*  Begin shutdown by first disabling any further  */
/*  interrupts from the hardware clock. This task  */
/*  suspends itself for 2/100th of a second to     */
/*  ensure interrupt is disabled.                  */

  rqdisable(CLOCK_INT_LEVEL, &status);
  rqsleep(2, &status);

/*  Send shutdown signals to all tasks and wait    */
/*  for all tasks to complete shutdown.            */

  rqsendunits(count_shtdwn_sem, 1, &status);
  rqsendunits(crt_shtdwn_sem, 1, &status);
  rqsendunits(clock_shtdwn_sem, 1, &status);
  
  rqreceiveunits(init_sem, 3, WAIT_FOREVER, &status);

/*  Cleanup before terminating. This includes      */
/*  deleting tasks and uncataloging/deleteing      */
/*  all objects created.                           */

  rqdeletetask(crt_tsk, &status);
  rqdeletetask(clock_tsk, &status);
  rqdeletetask(count_tsk, &status);
  rqdeletetask(timer_tsk, &status);
  
  rquncatalogobject(current_job,
    &clock_sem_name, &status);
  rquncatalogobject(current_job,
    &init_sem_name, &status);
  rquncatalogobject(current_job,
    &time_seg_name, &status);
  rquncatalogobject(current_job,
    &crt_req_mbx_name, &status);
  
  rquncatalogobject(current_job,
    &clock_shtdwn_sem_name, &status);
  rquncatalogobject(current_job,
    &count_shtdwn_sem_name, &status);
  rquncatalogobject(current_job,
    &crt_shtdwn_sem_name, &status);
  
  rqdeletesemaphore(clock_sem, &status);
  rqdeletesemaphore(init_sem, &status);
  
  rqdeletesemaphore(clock_shtdwn_sem, &status);
  rqdeletesemaphore(count_shtdwn_sem, &status);
  rqdeletesemaphore(crt_shtdwn_sem, &status);
  
  rqdeletesegment(sys_time_seg, &status);
  
  cursor_on();
  printf("\nAll Done!\n");
  
  exit(0);
  }
  
/*1*********************************************************
* Function: start_timer
*
* Summary:  Create and start a timer.
*
* Invocation: timer_sem = start_timer(timeout);
*
* Inputs:   timeout (unsigned int) - timer interval
*              in seconds
*
* Outputs:  timer_sem (selector) - the semaphore that will
*              receive a unit at each timer interval
*
* Caveats:  Accesses the time/date data segment that is
*           shared with the clock_task().
***********************************************************/

selector start_timer(unsigned int timeout)
  {
  unsigned int    status;
  unsigned int    indx;
  unsigned int    done;
  unsigned int    no_more_timers;
  
  selector    timer_sem;
  selector    current_job;
  selector    clock_sem;
  selector    sys_time_seg;
  
  struct SYS_TIME_STR   *sys_time;
  
  indx = 0;
  done = FALSE;
  no_more_timers = TRUE;
  timer_sem = NUL_TKN;

/*  Get objects for access semaphore and */
/*  time/date data segment               */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  clock_sem = rqlookupobject(current_job,
    &clock_sem_name, NO_WAIT, &status);
  sys_time_seg = rqlookupobject(current_job,
    &time_seg_name, NO_WAIT, &status);
  
  sys_time = buildptr(sys_time_seg, 0);

/* Access time/date memory  */

  rqreceiveunits(clock_sem, 1, WAIT_FOREVER, &status);

/* Find an empty timer slot  */

  while (!done)
    {
    if (!sys_time->timer[indx].in_use)
      {
      done = TRUE;
      no_more_timers = FALSE;
      }
    else
      {
      indx++;
      
      if (indx == MAX_TIMERS)
        {
        done = TRUE;
        }
      }
    }

/* Establish a timer  */

  if (no_more_timers == FALSE)
    {
    if (timeout ! = 0)
      {
      timer_sem = rqcreatesemaphore(SEM_INIT_0,
        999, FIFO_SEM, &status);
      sys_time->timer[indx].in_use = TRUE;
      sys_time->timer[indx].count = 0;
      sys_time->timer[indx].timeout = timeout;
      sys_time->timer[indx].sem_tkn = timer_sem;
      }
   }
/*  Release time/date memory */

  rqsendunits(clock_sem, 1, &status);

/*  Return timer semaphore to caller */

  return(timer_sem);
  }

/*1*********************************************************
* Function:  stop_timer
*
* Summary:   Delete a timer.
*
* Invocation:  timer_sem = start_timer(timeout);
*
* Inputs:    timeout (unsigned int) - timer interval
*              in seconds
*
* Outputs:   timer_sem (selector) - the semaphore that will
*              receive a unit at each timer interval
*
* Caveats:   Accesses the time/date data segment that is
*            shared with the clock_task().
***********************************************************/

unsigned int stop_timer(selector timer_sem)
  {
  unsigned int    status;
  unsigned int    indx;
  unsigned int    done;
  unsigned int    invalid_timer;
  
  selector    current_job;
  selector    clock_sem;
  selector    sys_time_seg;
  
  struct SYS_TIME_STR   *sys_time;
  
  indx = 0;
  done = FALSE;
  invalid_timer = TRUE;
  
/*  Get objects for access semaphore and */
/*  time/date data segment               */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  clock_sem = rqlookupobject(current_job,
    &clock_sem_name, NO_WAIT, &status);
  sys_time_seg = rqlookupobject(current_job,
    &time_seg_name, NO_WAIT, &status);
  
  sys_time = buildptr(sys_time_seg, 0);

/*  Access time/date memory.  */

  rqreceiveunits(clock_sem, 1, WAIT_FOREVER, &status);

/* Locate and remove this timer  */

  while (!done)
    {
    if (sys_time->timer[indx].in_use &&
      (sys_time->timer[indx].sem_tkn == timer_sem))
      {
      rqdeletesemaphore(timer_sem, &status);
      sys_time->timer[indx].in_use = FALSE;
      sys_time->timer[indx].sem_tkn = NUL_TKN;
      sys_time->timer[indx].count = 0;
      sys_time->timer[indx].timeout = 0;
      done = TRUE;
      invalid_timer = FALSE;
      }
    else
      {
      indx++;
      
      if (indx == MAX_TIMERS)
        {
        done = TRUE;
        }
      }
    }

/*  Release time/date memory  */
  
  rqsendunits(clock_sem, 1, &status);
  
/*  Return with result of call  */
  
  return (invalid_timer ? ERROR : EOK);
  }

/*1*********************************************************
* Task:     count_task
*
* Summary:  Continuously counts and displays a value from
*           0-65535.
*
* Caveats:  Communicates with the crt_task() for displaying
*           counter values.
***********************************************************/

void count_task(void)
  {
  unsigned char counter_buf[6];
  
  selector  current_job;
  selector  rsp_mbx;
  selector  crt_req_mbx;
  selector  init_sem;
  selector  count_shtdwn_sem;
  selector  req_seg;
  
  unsigned int  counter;
  unsigned int    status;
  
  struct CRT_REQ_STR    *req_msg;
  
  struct EXCEPTIONSTRUCT    exceptioninfo;
  
/*  Disable this task's exception handler  */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);

/*  Lookup/Create needed objects  */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  init_sem = rqlookupobject(current_job,
    &init_sem_name, NO_WAIT, &status);
  
  count_shtdwn_sem = rqlookupobject(current_job,
    &count_shtdwn_sem_name, NO_WAIT, &status);
  
  rsp_mbx = rqcreatemailbox(FIFO_OBJ_MBX, &status);
  
  crt_req_mbx = rqlookupobject(current_job,
    &crt_req_mbx_name, NO_WAIT, &status);

/*  Initialize request message  */

  req_seg = rqcreatesegment(
    sizeof(struct CRT_REQ_STR), &status);
  
  req_msg = buildptr(req_seg, 0);
  
  req_msg->msg_ptr = counter_buf;
  req_msg->row = 12;
  req_msg->col = 38;

/*  Singal that initialization is completed.  */

  rqsendunits(init_sem, 1, &status);

/*  Continuously count 0-65535 and display the  */
/*  value until signalled to stop.               */

  counter = 0;
  
  rqreceiveunits(count_shtdwn_sem, 1, NO_WAIT, &status);
  
  while (status == ETIME) /* while no signal received */
    {
    sprintf(counter_buf, "%d", counter);
    rqsendmessage(crt_req_mbx,
      req_seg, rsp_mbx, &status);
    req_seg = rqreceivemessage(rsp_mbx,
      WAIT_FOREVER, NUL_TKN, &status);
    counter++;
    rqreceiveunits(count_shtdwn_sem, 1, NO_WAIT, &status);
    }

/*  Cleanup objects created by this task. */

  rqdeletemailbox(rsp_mbx, &status);
  rqdeletesegment(req_seg, &status);

/*  Signal shutdown complete and suspend execution. */

  rqsendunits(init_sem, 1, &status);
  
  rqsuspendtask(NUL_TKN, &status);  /* suspend this task  */
  }

/*1************************************************************
* Task:     timer_task
*
* Summary:
*
* Caveats:
**************************************************************/

void timer_task(void)
  {
  selector    current_job;
  selector    init_sem;
  selector    clock_sem;
  selector    timer_sem;
  
  unsigned  int   status;
  
  struct EXCEPTIONSTRUCT  exceptioninfo;

/*  Disable this task's exception handler  */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);

/* Lookup needed objects  */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  init_sem = rqlookupobject(current_job,
    &init_sem_name, NO_WAIT, &status);

/*  Start a one-second timer. If the timer was      */
/*  created, wait for 20 seconds (units) then stop  */
/*  stop the timer.                                 */
  timer_sem = start_timer(1);
  
  if (timer_sem != NUL_TKN)
    {
    rqreceiveunits(timer_sem, 20, WAIT_FOREVER, &status);
    stop_timer(timer_sem);
    }

/*  Signal task finished and suspend execution.  */

  rqsendunits(init_sem, 1, &status);
  
  rqsuspendtask(NUL_TKN, &status); /* suspend this task */
  }

/*1**********************************************************
* Functions:   clrscrn, cursor_on, cursor_off, print_at
*
* Summary:   Functions for basic display control.
*
* Invocation:    clrscrn( );
*                curson_on( );
*                cursoff_on( );
*
* Invocation:    print_at(row, col, msg_ptr)
*
* Inputs:  row (unsigned int) - row position
*          col (unsigned int) - column position
*          msg_ptr (unsigned char *) - message string pointer
*
* Caveats: Escape sequences are for a WYSE60 terminal.
**************************************************************/

void clrscrn(void)
  {
  fprintf(stdout, "\x1B+");
  fflush(stdout);
  return;
  }

void cursor_on(void)
  {
  fprintf(stdout, "\x1B`1");
  fflush(stdout);
  return;
  }

void cursor_off(void)
  {
  fprintf(stdout, "\x1B'0");
  fflush(stdout);
  return;
  }

void print_at(unsigned int row,
  unsigned int col, unsigned char *msg_ptr)
  {
  fprintf(stdout, "\x1B=%c%c%s",
    (row + 31), (col + 31), msg_ptr);
  fflush(stdout);
  return;
  }

/*1*********************************************************
* Task:     crt_task
*
* Summary:
*
* Caveats:
***********************************************************/

void crt_task(void)
  {
  selector    current_job;
  selector    req_mbx;
  selector    rsp_mbx;
  selector    rec_seg;
  selector    init_sem;
  selector    crt_shtdwn_sem;
  
  unsigned int    status;
  
  struct CRT_REQ_STR    *req_msg;
  
  struct EXCEPTIONSTRUCT    exceptioninfo;
  
/*  Disable this task's exception handler */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);

/*  Lookup/Create needed objects  */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  init_sem = rqlookupobject(current_job,
    &init_sem_name, WAIT_FOREVER, &status);
  
  crt_shtdwn_sem = rqlookupobject(current_job,
    &crt_shtdwn_sem_name, WAIT_FOREVER, &status);
  
  req_mbx = rqcreatemailbox(FIFO_OBJ_MBX, &status);
  rqcatalogobject(current_job,
    req_mbx, &crt_req_mbx_name, &status);

/*  Initialize display  */

  cursor_off( );
  clrscrn( );

/*  Signal initialization complete  */

  rqsendunits(init_sem, 1, &status);
  
  
  rqreceiveunits(crt_shtdwn_sem, 1, NO_WAIT, &status);
  
  while (status == ETIME)
    {
    req_seg = rqreceivemessage(req_mbx,
      WAIT_FOREVER, &rsp_mbx, &status);
    
    req_msg = buildptr(req_seg, 0);
    
    print_at(req_msg->row,
      req_msg->col, req_msg->msg_ptr);
    
    if (rsp_mbx != NUL_TKN)
      {
      rqsendmessage(rsp_mbx, req_seg, NUL_TKN, &status);
      }
    else
      {
      rqdeletesegment(req_seg, &status);
      }
      
    rqreceiveunits(crt_shtdwn_sem, 1, NO_WAIT, &status);
    }
    
  rqsendunits(init_sem, 1, &status);
  
  for(;;)
    {
    req_seg = rqreceivemessage(req_mbx,
      WAIT_FOREVER, &rsp_mbx, &status);
      
    if (rsp_mbx != NUL_TKN)
      {
      rqsendmessage(rsp_mbx, req_seg, NUL_TKN, &status);
      }
    else
      {
      rqdeletesegment(req_seg, &status);
      }
    }
  }

/*1*********************************************************
* Function:  read_time
*
* Summary:   Read a specified time/date register of the
*            hardware clock.
*
* Invocation:  in_byte = read_time(reg);
*
* Inputs:    reg (unsigned char) - the hardware time/date
*              register to be read
*
* Outputs:   in_byte (unsigned char) - the value of the
*              register read
*
* Caveats:   None
***********************************************************/

unsigned char read_time(unsigned char reg)
  {
  unsigned char in_byte;
  
  outbyte(CNT_PORT, HOLD_HIGH);
  delay(3);
  outbyte(CNT_PORT, HOLD_READ);
  delay(1);
  outbyte(ADR_PORT, reg);
  delay(1);
  in_byte = inbyte(DAT_PORT);
  outbyte(CNT_PORT, CNT_RST);
  delay(1);
  
  return (in_byte);
  }

/*1*********************************************************
* Function:  write_time
*
* Summary:   Write a byte to the specified time/date register
*            of the hardware clock.
*
* Invocation:  write_time(reg, out_byte);
*
* Inputs:    reg (unsigned char) - the hardware time/date
*              register
*
*           out_byte (unsigned char) - the value to be
*             written to the hardware clock register
*
* Caveats:  None
***********************************************************/

void write_time(unsigned char reg, unsigned char out_byte)
  {
  outbyte(CNT_PORT, HOLD_HIGH);
  delay(3);
  outbyte(ADR_PORT, reg);
  delay(1);
  outbyte(DAT_PORT, out_byte);
  delay(1);
  outbyte(CNT_PORT, HOLD_WRITE);
  delay(1);
  outbyte(CNT_PORT, CNT_RST);
  delay(1);
  
  return;
  }

/*1*********************************************************
* Function:  read_clock
*
* Summary:   Read the time/date from the hardware clock and
*            stores it in the global time/date structure.
*
* Invocation:  read_clock();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
***********************************************************/

void read_clock(void)
  {
  unsigned char time_buf[13];
  unsigned int i;

/*  Set mode */

  outbyte(PIO_PORT, PIO_READ);
  delay(1);

/*  Set control lines to disable interrupts */

  outbyte(CNT_PORT, CNT_RST);
  delay(1);

/*  Read time/date */

  for (i = 0 ; (i <= 12); i++)
    {
    time_buf[i] = read_time(i);
    }
  
  sys_time->second =
    (10 * (time_buf[SEC10_REG] & 0x07)) +
    (time_buf[SEC01_REG] & 0x0F);
  sys_time->minute =
    (10 * (time_buf[MIN10_REG] & 0x07)) +
    (time_buf[MIN01_REG] & 0x0F);
  sys_time->hour =
    (10 * (time_buf[HRS10_REG] & 0x03)) +
    (time_buf[HRS01_REG] & 0x0F);
  sys_time->date =
    (10 * (time_buf[DAT10_REG] & 0x03)) +
    (time_buf[DAT01_REG] & 0x0F);
  sys_time->month =
    (10 * (time_buf[MON10_REG] & 0x01)) +
    (time_buf[MON01_REG] & 0x0F);
  sys_time->year =
    (10 * (time_buf[YRS10_REG] & 0x0F)) +
    (time_buf[YRS01_REG] & 0x0F);

/*  Reset control lines to enable interrupts */

  outbyte(CNT_PORT, INT_ENB);
  delay(1);
  
  outbyte(ADR_PORT, 0x0F);
  delay(1);
  
  return;
  }

/*1*********************************************************
* Function:  write_clock
*
* Summary:   Uses the global time/date structure to write a
*            new time/date to the hardware clock.
*
* Invocation:  write_clock();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
************************************************************/

void write_clock(void)
  {
  unsigned char temp_byte;

/*  Set mode */

  outbyte(PIO_PORT, PIO_WRITE);
  delay(1);

/*  Set control lines to disable interrupts */

  outbyte(CNT_PORT, CNT_RST);
  delay(1);

/*  Write time/date. Seconds always set to 0 */

  sys_time->second = 0;
  write_time(SEC01_REG, 0);
  write_time(SEC10_REG, 0);
  
  temp_byte =
    sys_time->minute - (10 * (sys_time->minute / 10));
  write_time(MIN01_REG, temp_byte);
  temp_byte = sys_time->minute / 10;
  write_time(MIN10_REG, temp_byte);
  
  temp_byte =
    sys_time->hour - (10 * (sys_time->hour / 10));
  write_time(HRS01_REG, temp_byte);
  temp_byte =
    (sys_time->hour / 10) | 0x08;     /* 24hr format */
  write_time(HRS10_REG, temp_byte);
  
  temp_byte =
    sys_time->date - (10 * (sys_time->date / 10));
  write_time(DAT01_REG, temp_byte);
  temp_byte = sys_time->date / 10;
  write_time(DAT10_REG, temp_byte);
  
  temp_byte =
    sys_time->month - (10 * (sys_time->month / 10));
  write_time(MON01_REG, temp_byte);
  temp_byte = sys_time->month / 10;
  write_time(MON10_REG, temp_byte);
  
  temp_byte =
    sys_time->year - (10 *(sys_time->year / 10));
  write_time(YRS01_REG, temp_byte);
  temp_byte = sys_time->year / 10;
  write_time(YRS10_REG, temp_byte);

/*  Reset mode*/

  outbyte(PIO_PORT, PIO_READ);
  delay(1);

/*  Reset control lines to enable interrupts */

  outbyte(CNT_PORT, INT_ENB);
  delay(1);
  
  outbyte(ADR_PORT, 0x0F);
  delay(1);
  
  return;
  }

/*1*********************************************************
* Function:  reset_timers
*
* Summary:   Initializes (zero out) all timer slots of the
*            shared time/date data segment.
*
* Invocation:  reset_timers();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
************************************************************/

void reset_timers(void)
  {
  unsigned int    i;
  
  for (i = 0 ; i < MAX_TIMERS; i++)
    {
    sys_time->timer[i].in_use = FALSE;
    sys_time->timer[i].sem_tkn = NUL_TKN;
    sys_time->timer[i].count = 0;
    sys_time->timer[i].timeout = 0;
    }
  
  return;
  }

/*1*********************************************************
* Function:  advance_timers
*
* Summary:   Increments all active timers in the shared
*            time/date data segment by one second.
*
* Invocation:  advance_timers();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
***********************************************************/

void advance_timers(void)
  {
  unsigned int    i;
  unsigned int    status;
  
  
  for (i = 0 ; i < MAX_TIMERS; i++)
    {
    if (sys_time->timer[i].in_use)
      {
      sys_time->timer[i].count++;
      if (sys_time->timer[i].count >=
        sys_time->timer[i].timeout)
        {
        rqsendunits(
          sys_time->timer[i].sem_tkn, 1, &status);
        sys_time->timer[i].count = 0;
        }
      }
    }
  
  return;
  }


/*1**********************************************************
* Function:  advance_time_date
*
* Summary:   Increments the time/date (hh:mm:ss mm/dd/yy) in
*            the shared time/date data segment by one second.
*
* Invocation:  advance_time_date();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
************************************************************/


void advance_time_date(void)
  {
  if (sys_time->second < 59)
    {
    sys_time->second++;
    
    }
  else
    {
    sys_time->second = 0;
    
    if (sys_time->minute < 59)
      {
      sys_time->minute++;
      }
    else
      {
      sys_time->minute = 0;
      
      if (sys_time->hour < 23)
        {
        sys_time->hour++;
        }
      else
        {
        sys_time->hour = 0;
        
        if (sys_time->date <
          days_in_month[sys_time->month - 1])
          {
          sys_time->date++;
          }
        else
          {
          if (sys_time->month == FEBRUARY)
            {
/*  Check for leap year (only good through 2100)  */

            if (((sys_time->year % 4) == 0) &&
              (sys_time->date == 28))
              {
              sys_time->date = 29;
              }
            else
              {
              sys_time->month = MARCH;
              sys_time->date = 1;
              }
            }
          else
            {
            sys_time->date = 1;
            
            if (sys_time->month < DECEMBER)
              {
              sys_time->month++;
              }
            else
              {
              sys_time->month = JANUARY;
              
              if (sys_time->year < 99)
                {
                sys_time->year++;
                }
              else
                {
                sys_time->year = 0;
                }
              }
            }
          }
        }
      }
    }
  
  return;
  }


/*1*********************************************************
* Function:  start_timer
*
* Summary:   Create and start a timer.
*
* Invocation:  timer_sem = start_timer(timeout);
*
* Inputs:    timeout (unsigned int) - timer interval
*             in seconds
*
* Outputs:   timer_sem (selector) - the semaphore that will
*             receive a unit at each timer interval
*
* Caveats:   The interrupt handler will signal the
*            interrupt task to finish servicing the
*            interrupt.
************************************************************/

/* Tell the compiler that this function is an interrupt    */
/* routine so it can generate proper cede for entering     */
/* and exiting the interrupt routine.                      */

#pragma interrupt("clockint_handler")

void clockint_handler(void)
  {
  unsigned int status;

/*  Signal the interrupt task */

  rqsignal interrupt(CLOCK_INT_LEVEL, &status);
  
  return;
  }

/*1*********************************************************
* Task:     clock_task
*
* Summary:  Services interrupts from the hardware clock.
*           Also maintains/updates the global time/date
*           data and services software timers.
*
* Caveats:  Communicates with the crt_task() to display the
*           current time/date.
***********************************************************/

void clock_task(void)
  {
  char time_string[19];
  
  unsigned int status;
  unsigned int retry;
  
  selector current_job;
  selector init_sem;
  selector clock_sem;
  selector clock_shtdwn_sem;
  
  selector rsp_seg;
  selector req_seg;
  selector crt_req_mbx;
  selector rsp_mbx;
  
  struct CRT_REQ_STR       *req_msg;
  
  struct EXCEPTIONSTRUCT      exceptioninfo;

/*  Disable this task's exception handler */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);

/*  Disable any current interrupt task */

  rqresetinterrupt(CLOCK_INT_LEVEL, &status);

/*  Lookup/Create needed objects */

  current job = rqgettasktokens(THIS_JOB, &status);
  
  init_sem = rqlookupobject(current_job,
    &init_sem_name, NO_WAIT, &status);
  
  clock_sem = rqlookupobject(current_job,
    &clock_sem_name, NO_WAIT, &status);
  
  sys_time_seg = rqlookupobject(current_job,
    &time_seg_name, NO_WAIT, &status);
  
  clock_shtdwn_sem = rqlookupobject(current_job,
    &clock_shtdwn_sem_name, NO_WAIT, &status);
  
  crt_req_mbx = rqlookupobject(current_job,
    &crt_req_mbx_name, NO_WAIT, &status);
  
  sys_time = buildptr(sys_time_seg, 0);
  
  rsp_mbx = rqcreatemailbox(FIFO_OBJ_MBX, &status);

/*  Initialize time string buffer  */

  memset(&time_string, ' ' ,sizeof(time_string));

/*  Initialize crt_task() request message and  */
/*  send it to the response mailbox to setup   */
/*  for the processing loop.                   */

  req_seg = rqcreatesegment(
    sizeof(struct CRT_REQ_STR), &status);
  req_msg = buildptr(req_seg, 0);
  
  req_msg->msg_ptr = &time_string;
  req_msg->row = 1;
  req_msg->col = 60;
  
  rqsendmessage(rsp_mbx, req_seg, NUL_TKN, &status);

/*  Initialize retry counter and timers */

  retry = 0;
  sys_time->change_flg = FALSE;
  reset_timers();

/*  Initialize and read hardware clock */

  outbyte(PIO_PORT, PIO_READ);
  delay(1);
  
  outbyte(CNT_PORT, INT_ENB);
  delay(1);
  
  outbyte(ADR_PORT, 0x0F);
  delay(1);
  
  read_clock();

/*  Allow access to time/date memory */

  rqsendunits(clock_sem, 1, &status);

/*  Set interrupt vector  */

  rqsetinterrupt(CLOCK_INT_LEVEL, 1,
    &clockint_handler, NUL_TKN, &status);

/*  Signal initialization complete */

  rqsendunits(init_sem, 1, &status);

/*  Process interrupt signals until shutdown */

  rqreceiveunits(clock_shtdwn_sem, 1, NO_WAIT, &status);
  
  while (status == ETIME)  /* while no signal received */
    {
/*  Wait no longer than 2 seconds for interrupt signal */
   
   rqetimedinterrupt(CLOCK_INT_LEVEL, 200, &status);

/*  If an interrupt is NOT received, attempt to reset  */
/*  the hardware clock.                                */

   if (status == ETIME)      /* if no interrupt  */
     {
     retry++;

/*  Once the maximum number of retries is reached,    */
/*  give up. Retrieve outstanding display request     */
/*  and wait for shutdown. When shutdown signal is    */
/*  received, disable this interrupt task, signal     */
/*  shutdown complete and suspend execution.          */

     if (retry == MAX_CLOCK_RETRIES)
       {
       req_seg = rqreceivemessage(rsp_mbx,
         WAIT_FOREVER, NUL_TKN, &status);
       rqreceiveunits(clock_shtdwn_sem, 1,
         WAIT_FOREVER, &status);
       rqresetinterrupt(CLOCK_INT_LEVEL, &status);
       rqsendunits(init_sem, 1, &status);
       rqsuspendtask(NUL_TKN, &status);
       }

/*  Reinitialize hardware clock */

       outbyte(PIO_PORT, PIO_READ);
       delay(1);
       
       outbyte(CNT_PORT, INT_ENB);
       delay(1);
       
       outbyte(ADR_PORT, 0x0F);
       delay(1);

/*  Re-read hardware crock */

       rqreceiveunits(clock_sem, 1,
         WAIT_FOREVER, &status);
       
       read_clock();
       
       rqsendunits(clock_sem, 1, &status);
       }
     else
       {
/*  Interrupt signal received, reset retry count */

       retry = 0;

/*  Access time/date memory */

       rqreceiveunits(clock_sem, 1,
         WAIT_FOREVER, &status);

/*  If needed, update hardware clock with new  */
/*  time/date data. Otherwise, advance the     */
/*  time/data data by one second.              */

       if (sys_time->change_flg)
         {
         write_clock();
         sys_time->change_flg = 0;
         }
       else
         {
         advance_time_date();
         }

/*  Advance any timers by one second  */

       advance_timers();

/*  Prepare time/date data for display  */

     sprintf(time_string, "%02d:%02d:%02d %02d/%02d/%O2d",
       sys_time->hour, sys_time->minute, sys_time->second,
       sys_time->month, sys_time->date, sys_time->year);

/*  Release time/date memory */

     rqsendunits(clock_sem, 1, &status);

/*  If previous display request has finished(segment    */
/*  returned), then request new time/date be displayed  */

     rsp_seg = rqreceivemessage(rsp_mbx,
       NO_WAIT, NUL_TKN, &status);
     
     if (status != ETIME)
       {
       rqsendmessage(crt_req_mbx,
         req_seg, rsp_mbx, &status);
       }
     }
/*  Check for shutdown  */

   rqreceiveunits(clock_shtdwn_sem, 1, NO_WAIT, &status);
   }

/*  Retrieve outstanding display request, disable   */
/*  this interrupt task, signal shutdown complete   */
/*  and suspend execution.                          */

  req_seg = rqreceivemessage(rsp_mbx,
    WAIT_FOREVER, NUL_TKN, &status);
  
  rqsendunits(init_sem, 1, &status);
  
  rqresetinterrupt(CLOCK_INT_LEVEL, &status);
  
  rqsuspendtask(NUL_TKN, &status);  /* suspend this task  */
  }

January 1991/The iRMX Family Of Operating Systems/Sidebar

iRMX C Code For 80x86 Processors


The ANSI C compiler for the iRMX operating system provides many features for working with the 80x86 family of processors. The selector data type is used to designate tokens that basically correspond to the addresses of object descriptors. The selector type could be replaced with the unsigned int type. The example uses the selector type because it is used by all system call prototypes in RMXC. H. The #pragma interrupt directive tells the compiler to generate the proper prologue and epilogue (e.g., IRET) for an interrupt function. The are also many built-in functions for dealing directly with the 80x86 processor. In this example, inbyte and outbyte functions are used for reading and writing I/O ports.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.