Dec 092017
Real Time Executive written in C language and 8086 Assembly from The Computer Applications Journal ( Circuit Cellar Ink).
File RTX.ZIP from The Programmer’s Corner in
Category C Source Code
Real Time Executive written in C language and 8086 Assembly from The Computer Applications Journal ( Circuit Cellar Ink).
File Name File Size Zip Size Zip Type
CUSTOM.H 1891 514 deflated
DEMO.C 16184 3963 deflated
DEMO.OBJ 11761 6439 deflated
G.BAT 65 55 deflated
PROGSPEC.DOC 17559 5167 deflated
R.BAT 171 134 deflated
REGS 28221 4254 deflated
REGS.ASM 29541 4681 deflated
REGS.OBJ 3867 2465 deflated
RTX.ASM 52304 8872 deflated
RTX.C 24646 4621 deflated
RTX.EXE 42020 17756 deflated
RTX.H 8189 1925 deflated
RTX.MAK 1158 459 deflated
RTX.OBJ 17491 9324 deflated
STDDEFS.ASM 11678 1588 deflated

Download File RTX.ZIP Here

Contents of the PROGSPEC.DOC file


Programming Reference Notes

Written by: Mike Podanoffsky


Technical Questions: Please refer all technical questions to
the author at

12 South Walker Street
Lowell, MA 01851

Telephone: 508/ 454-1620


I. General Notes

Be sure to specify the actual values in custom.h for maximum number
of tasks and timers. Any custom events must be added to the custom.h
events list. System events may be added to rtx.h files.

II. Building A System

rtx.mak shows all of the commands necessary to make all components of
the real time system. Basically you must recompile any changes made
to custom.h and rtx.h with the rest of the real time system. Then,
link your code with the rtx.obj and regs.obj modules.

III. Components Not Supplied

There are no interrupt service routines supplied with the code
except for a minimal keyboard interrupt service routine.

IV. Source Modules

rtx.h contains all prototypes for functions and data structures used
by the real-time system.

rtx.c is the main source module and contains almost all of the routines
for the real-time system.

regs.asm contains some assembly language routines. This is because
register saving cannot be done well within C.

V. Guide To Functions

Task Pointer/ Task Id

Almost all functions use a task pointer. This is a pointer to a
structure of data maintained by the real-time executive for controlling
a task. Task ids is an offset into this table. Sometimes it is
more convenient to use the id, other times the task pointer.

To convert a task id to a task pointer, use the following syntax

&Tasks[ id ]

To convert a task pointer to an id, use the following macro

#define TASK_ID( TaskPtr ) \

(int )((TaskPtr) - &Tasks[0] )

Initializing/ Termination Tasks

The entire real-time system needs to be initialized by initTaskSystem().

The defineTask() function initializes task parameters and allocates
a stack from the heap. The stack will contain both the start address
of the task and a return address to terminateTask_id. A return from
the task will terminate the task.

terminateTask_id() will free the allocated stack and free zero the
task's priority. The task will not run again until redefined.

You can find a task by name using the findTask function. One powerful
use of the naming convention is to create services. For example,
a printer service could be called "Printer". To find the task pointer
of the printer service, use the findTask() function.


terminateTask(Task far * );
terminateTask_id(int task_id );
suspendTask(Task far * task, int cond );

Task far * findTask(char far *name);

Task far * defineTask(

int (pascal *task_start_addr) (void far * arg ),
void far * argument,
unsigned stack_size,
int priority,
char far * task_name );


task_start_addr address of task function
argument argument passed to task function
stack_size stack size in bytes
priority task priority
task_name pointer to task name

Get/Change Priority

Tasks have alterable priorities. A NULL Task pointer is evaluated
to the current task.

int getPriority(Task far * task );
int setPriority(Task far * task, int prio );

Queue Managers

Each task contains a queue. Queueing any data to the task is the
equivalent of setting an event for the task. Tasks with queued
data get a chance to run. This queue mechanism is maintained to
support printer spooler and other task to task transaction processing

A queue consists of a two queue pointers, one points to the start
of the queue, the other to the end of the queue. The start of the
queue is the start of a linked chain of data. The starting queue
pointer points to any data. The first element of that data is assumed
to be a queue pointer to the next queued data block. A NULL pointer
is the end of the queued list.

queueEvent(Task far * task, void far *ptr );
void far * getnext_queueEvent(Task far * task );
void far * isnext_queueEvent(Task far * task );

Set Events/Wait Events

defineEventFct() defines a function to test the SystemEvents bit for
a specific event id. You can set, clear, and toggle system events
that do not have a defined event function by using functions
setSystemEvent(), clearSystemEvent(), and toggleSystemEvent().

A task can be set to wait for an event by the function waitEvent().
A NULL task pointer is converted to the current task.


event event_id,
int (pascal *fct) (event event_id, void far * arg ),
void far * argument );

int setSystemEvent( unsigned event_id );
int clearSystemEvent( unsigned event_id );
int toggleSystemEvent( unsigned event_id );

int waitEvent(Task far * task, unsigned event_id );

event getActiveEvent(Task far * task )


sleepTask() and sleepTill() will sleep a task until the specified
number of clock ticks or the time of day has expired.

int sleepTask(Task far * task, int ticks );
int sleepTill(Task far * task, long time_of_day );


You can create timer functions. A timer is armed with a value and
it will call the timer function when the timer value has expired.

int setTimer_id(

int timer_id,
unsigned flag,
unsigned long init_value,
int (pascal *action) ( void far * arg ),
void far * argument );

int setTimer(
unsigned flag,
unsigned long init_value,
int (pascal *action) ( void far * arg ),
void far * argument );

int clearTimer(int timer_id );
int clearTimer_id(int timer_id );

Internal Functions

These functions are used internally only.

void waitKbdEvent(void );
void setKbdEvent(void );
void far * returntask_StackFrame(void );
void saveCurrentTask_Stack(void far * stack);
static int event_match( event far * events );
void far * scheduleTask(void );

void countDown_Timers(void );
void setClockIntValue(void );

VI. Inter Task Communications/ Sharing Data

Often tasks depend and must communicate with each other. A task may
want to start or terminate other tasks. One example of this is my
earlier problem with networks. A task needed to start another task
copying all data in a temporary file to the network when the network
was running.

Because of the event mechanism starting a waiting task is simple.
An event flag is defined. One task sets the event flag and calls
the scheduler. The scheduler will startup the task like any other

Passing data between tasks is more a matter of convention than of
system support. Both tasks could agree that specific variables or
structures will be used to communicate between tasks. This would
constitute a voluntary or overt data sharing and it will always
work between the two tasks so long as each task updates the data
in this shared spaced according to established rules.

Sharing data invariably constitutes of a writting task and at least
one reading task. That is, one task generates and writes the data to
the shared space while the another task reads the data.

To stay out of trouble, make sure you write data in ways in which
the reading task cannot get confused. The obvious example is when
filling a buffer place the dat in the buffer before updating a buffer
count or pointer. For more complex cases you may need to disable
interrupts during the update process. This is generally the safest
mechanism for updating shared data.

This simple rule is violated in many unconscious ways and "C" is
particularly facilitating in this regard. For example, the code
in Figure 2a shows how utterly simple it is to update a counter
in a buffer before placing the contents in the buffer.


buffer[ store_buffer_count++ ] = c;

mov si,word ptr [ store_buffer_count ]
inc word ptr [ store_buffer_count ]
mov al,byte ptr [ c ]
mov byte ptr [ buffer + si ],al

Figure 2a. Inadvertently updating a count before placing data
in a shared buffer.


buffer[ store_buffer_count ] = c;

mov si,word ptr [ store_buffer_count ]
mov al,byte ptr [ c ]
mov byte ptr [ buffer + si ],al

inc word ptr [ store_buffer_count ]

Figure 2b. Correct way to update dat in a shared buffer.

If the updating task is interrupted, that interruption could occur
at any time and between any two machine level instructions including
any two critical instructions. You will experience occasional garbage
characters or occassional unexplained behavior which is almost always
the result of miscommunicated data.

Disabling Interrupts And the Scheduler

The problems associated with sharing data can be eliminated by
disabling interrupts during the data update process. Two functions
are provided to disable and reenable interrupts, disableInts() and

By disabling interrupts you disable any event that may cause the
scheduler to run. Your task remains in control and no other task
has a chance to run.

An alternative would be to disable the scheduler itself and leave
hardware interrupts alone. This is preferential intellectually.
The scheduler is disabled and enabled by the disableSched() and
enableSched() functions.


buffer[ store_buffer_count++ ] = c;

call disableInts
mov si,word ptr [ store_buffer_count ]
inc word ptr [ store_buffer_count ]
mov al,byte ptr [ c ]
mov byte ptr [ buffer + si ],al
call enableInts

Figure 2c. Disable/Enable interrupts.


buffer[ store_buffer_count++ ] = c;

call disableSched
mov si,word ptr [ store_buffer_count ]
inc word ptr [ store_buffer_count ]
mov al,byte ptr [ c ]
mov byte ptr [ buffer + si ],al
call enableSched

Figure 2c. Disable/Enable just the scheduler.

Other Problems With Sharing Data

There is a potentially more serious problem in a multitasking
system, that of inadvertant data sharing. This is different from
the above mentioned problem of intentional data sharing.

When a product is built, all tasks are linked together with the
real time executive. Global data declared for any one task is
available to all tasks and inadvertant data sharing can and does
unfortunately happen.

One easy way to dramatize the problem of sharing data is to assume
a variable 'n' used as a loop counter. Two tasks inadvertantly use
the variable. While the first task is in the loop counting down the
value in variable 'n' the task is interrupted to run the other task.
The second task resets and uses the variable. The results for the first
task, when reactivated, will be unamussing and unpredictable.

If you are not careful about sharing variables there may be behavior
within your application that at best you don't understand and at worst
highly sporadic.

Other than being careful, you can avoid inadvertant data sharing by
using and allocating variables in a stack or on the heap. Each task
is given a separate stack so variable allocated on the stack are
safe from inadvertant data sharing.

Within C, variables are made private by declaring them on the stack
or within {...}. Declaring them 'static' will make them sharable.

A 'static' declaration will make it private for the source module in
which it appears. So long as you know that this code is not shared
by other tasks then you have a private variable. Figures 1a and 1b
shows how to make variables private by declaring them on the stack.


/* all of these are shareable across tasks */

static int n;
extern int k;
int p;
int q = 0;

char far * ptr;

Figure 1a. Sharable uses of variables.

All of these examples make these variables sharable across tasks.
A 'static' variable is only known inside this module, but the code
in this module may belong to more than one task.


/* declare variables inside functions to make them private */

int any_routine()
int n, k, p, q = 0;
char far * ptr;



Figure 1b. Private use of a variable.

Having cautioned you about inadvertant data sharing let me state that
deliberate data sharing is not bad. You may want, so long as you
observe certain rules, to share data. One example of this is an
interrupt service routine that stores the current status of the
hardware in a common location. The "rule" on data sharing is that
the value is always just modified by a single piece of code, the
interrupt service routine. It can then be read by any task.

In the newswire capture application a common buffer for storing
received data was shared between the receive driver and processing
task. To minimize problems rules need to be established as to
how the data was accessible. A single routine accessed data.
Interrupts were disabled when the character was retrieved and the
character count changed. This was a short period of time but it
meant that the interrupt service routine would not have a chance
to access this shared data.

Code Sharing

Interestingly, while you need to be very careful about sharing data
between tasks, there is no restriction on sharing code. Our real-time
multitasking system imposes no restriction whatsoever on code sharing
and in fact may actually happen all the time in ways which you may
not have realized.

Code sharing can and does happen. Assume that task A is running
executing a very simple function max(). Max accepts two values and
returns the higher of the two. When max() is called within task A,
the two arguments are pushed on the stack. If at any time task A is
interrupted while inside max(), the stacked values and any register
values are preserved for task A. If task B is started while task A
is interrupted, it may execute the same function max() with values
stored in a different stack. Code is sharable because the data for
each task is kept separate.

 December 9, 2017  Add comments

Leave a Reply