home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Frostbyte's 1980s DOS Shareware Collection
/
floppyshareware.zip
/
floppyshareware
/
DOOG
/
CTASK.ZIP
/
CTASK.DOC
< prev
next >
Wrap
Text File
|
1990-01-02
|
215KB
|
5,312 lines
CTask
A Multitasking Kernel for C
Version 2.0 Released 89-12-24
Public Domain Software written by
Thomas Wagner
Ferrari electronic GmbH
Contents
About this Manual 1
Introduction 1
An Example 1
Switching the Context 2
You have Mail 2
Reentrancy and Resources 3
DOS Access 3
Handling the Keyboard 4
Serial I/O and Timeouts 4
Priorities 5
Change to your liking 5
General Notes 6
What can CTask NOT be used for? 6
What is required to use CTask? 6
Do I have to pay for using CTask? 7
What support can I expect? 8
About this Release 9
Multitasking Basics 10
Tasks 10
Events 10
Reentrancy 11
Deadlocks 12
Using CTask 14
Configuration Options 14
Memory Allocation 16
Snapshot 17
Task Stacks 17
Drivers 18
Things to remember 18
Priority Handling 19
Multitasking and DOS 20
Spawning and CTask TSR's 21
Task Groups 22
How does CTask work 24
Queues 24
The Scheduler 25
Events 25
Resources 25
Flags 26
Counters 26
Mailboxes and Pipes 26
Serial and Printer Drivers 27
Ctask Manual - Version 2.0 - 89-12-21 - Contents 1
CTask Data Types 29
Typedefs used for simplified type specifications 30
Error return values for event wait functions 30
Queues 31
The timer/watch control block 32
The name link structure 36
The task control block structure 36
Task states 38
Task flags 39
The Group Control Block 40
The event control blocks 40
The Ticker structure 41
The flag event structure 41
The counter event structure 42
The resource event structure 42
The mailbox event structure 43
The pipe and word pipe event structure 43
The buffer event structure 44
CTask Routines 45
Installation and Removal 45
Searching for names 47
Miscellaneous 48
Task Operations 50
Timer Operations 52
Event wait Timeouts 53
"Tickers" 53
Delays 54
Timed Events and Watch Events 54
Event Operations 59
Resources 59
Flags 61
Counters 62
Mailboxes 64
Pipes 65
Buffers 67
The Keyboard Handler 69
The Serial I/O handler 69
The Printer Output Driver 75
Some notes on potential trouble spots 77
Turbo C console output 77
The timer tick EOI 77
Debugging 77
Changes from Previous Versions 79
Changes for CTask 1.2 to 2.0 79
Interface Changes 79
Changes for CTask 1.1b to 1.2 81
Changes for CTask 1.1 to 1.1b 83
Changes for CTask 0.1 to 1.1 83
Ctask Manual - Version 2.0 - 89-12-21 - Contents 2
About this Manual
If you are new to CTask, I would suggest reading all chapters
before attempting to build your first application. One chapter
you can skip is the CTask Data Types section, since you are not
required to know about the innards of CTask's structures. You can
also ignore the List of Changes.
If you are updating from an older release, the List of Changes
at the end is for you. You can safely skip the Introduction, and
the section on Multitasking Basics. In the General Notes, you
should read the section on support and the info about this
release. In the Using CTask section, check the new configuration
options, and the chapter on Memory Allocation. Check the Routine
descriptions, and, if you are using internal data structures
directly, the Data Type descriptions, for changes.
Finally, a note to German speaking readers:
Dieses Handbuch, wie auch die Kommentare im Quellcode, ist in
Englisch abgefaßt, da dies die universelle Sprache für Computer-
benutzer weltweit ist. Es gibt keine deutsche Version, und es
wird auch keine geben (sofern nicht jemand bereit ist einen
angemessenen Betrag für eine Übersetzung auf den Tisch des Hauses
zu legen). Selbstverständlich bleibt es Ihnen unbenommen, das
Handbuch selbst zu übersetzen und diese Version auch zu
vertreiben, oder mir zum Vertrieb anzubieten. Wie die Software
ist auch dieses Handbuch Public Domain.
Introduction
CTask is a set of routines that allow your C program to execute
functions in parallel, without you having to build in sophisti-
cated polling and switching schemes. CTask handles the switching
of processor time with a priority based, preemptive scheduler,
and provides a fairly complete set of routines for inter-task
communication, event signalling, and task interlocking. CTask
also includes a number of drivers for MS-DOS that build on the
basic functions to allow you to include serial I/O, printer
buffering, and concurrent access to DOS functions into your
programs with little programming effort.
An Example
To illustrate one possible use of CTask, let me elaborate on the
following example. Say you just finished your nifty telecommuni-
cations program, complete with download protocols, scripts, and
everything. But wouldn't it be nice to be able to print the file
you just downloaded while receiving the next, and edit a comment
Ctask Manual - Version 2.0 - 89-12-21 - Page 1
to some previous message without interrupting the transmission?
So you take those editor routines from a previous project, plug
them in, and - oops, how do you switch back and forth between
editing, communication and printing? The answer to this is CTask.
CTask allows your C program to do many different things at the
same time by switching the processor between the tasks you de-
fine. And since most of the time your program is waiting for some
slow device (like the human hand) to provide feedback, this
switching is completely transparent, and will not noticeably slow
your program down.
Switching the Context
So what is needed to allow the user to edit a file while at the
same time downloading another and printing a third? First, you
have to have some form of "context switching". This means that
you have to be able to interrupt the processing of the download
when the user presses a key, process the key, and return to the
download "task" at the exact same point it was interrupted. One
solution to this would be to include a poll for the keyboard at
several points in the download routine, and call the editor task
when a key is available. But apart from cluttering your code
with lots of unrelated calls, there is another problem. What if
the operation the user requested is more involved than just
putting the character on the screen, like writing the file to
disk? This might take so long that your download times out. There
must be a way to pass control back and forth between the two
tasks, such that no task is delayed for an extended period of
time, and also to activate the print spooler task at some defined
interval to output the data to the printer. This context
switching is called "scheduling" in CTask. The "scheduler" is
invoked on every system timer tick, and will save the context of
the current task. The scheduler then takes the first element from
the queue of tasks that are eligible to be run, and restores the
context of this task, returning to the point where the task was
interrupted. This switching is completely automatic, and requires
no special programming in the tasks itself. All you have to do is
to tell CTask that there are three tasks, the download task, the
spooler task, and the editor task.
You have Mail
All you have to do for context switching, that is. There's a bit
more to multitasking than meets the eye. How do you tell the
spooler task what files to spool, and the download task what
files to download? You can't call a task like an ordinary func-
tion, so what you need for this is "inter-task communication".
There must be a way to pass a message containing the filename to
be printed to the spooler task, and there are several in CTask,
one of them the "mailbox". The spooler can use a CTask call,
wait_mail, to wait for a message to arrive at its mailbox. As
Ctask Manual - Version 2.0 - 89-12-21 - Page 2
long as nothing arrives, the spooler task will no longer be
scheduled, so if there is nothing to print, it will not use any
processor time. When you send a message with send_mail to the
mailbox, the spooler will wake up, and process the file. You can
also send more file name messages to the spooler while it still
prints a file, leaving the messages in the mailbox until the
spooler is ready to process the next file.
Reentrancy and Resources
This last example seems innocent enough, but there's a big stumb-
ling block hidden in it. You allocate the file name messages in
the controlling task with malloc, and you free them in the
spooler with free, no problem, right? Wrong, there is a big
problem, "reentrancy". Reentrancy means that you can re-enter a
routine while another task is already using it, and that this
will not disturb the operation of the interrupted task. But
malloc and free share and modify global data, the chain of free
memory blocks. Imagine the following: You just called malloc from
the controlling task. Malloc has loaded the address of a free
element into a local variable, and is about to write back the
pointer to the next free element into the last. At exactly this
moment, the timer ticks, and the spooler is activated. It has
just finished printing, so it calls free. Free steps through the
chain of free blocks to find the right place to insert the block.
According to Murphy's law, it will find just the place where
malloc is about to write back the pointer. Free coerces the
elements, points the next pointer to the element malloc just
wants to take off the chain, and returns. Malloc writes its next
pointer into the middle of the newly coerced block, and now re-
turns an element which is still in the free list. Compared to the
job of finding this kind of bug, stepping in for Tantalus may
feel like a vacation. This kind of problem code is called a
"critical region". There must be a way to make sure that no two
tasks simultaneously enter such a region, and, you guessed it,
CTask provides one, the "resource". When you request a resource
in one task, all other tasks trying to request the same resource
after that are put to sleep until you call release_resource. Only
then will the highest priority task that waits for the resource
wake up, and get access to the protected region. So you would
have to substitute malloc and free calls in your routines with
calls to functions that first request a resource, execute the
function, and then release the resource.
DOS Access
But, you might ask, isn't there another reentrancy problem in
this example, since both the spooler and the download task might
simultaneously call DOS to do their file-I/O, and DOS is not
reentrant? Do I have to substitute all my calls to fread and
fwrite, too? The answer to this, luckily, is no. CTask traps all
Ctask Manual - Version 2.0 - 89-12-21 - Page 3
your DOS calls, and automatically encloses them in the necessary
resource request and release calls, so you don't have to worry
about trashing your disk by simultaneous DOS requests. The
limited multitasking capabilities of DOS are exploited to allow
some parallel processing in DOS, and CTask will also detect and
handle DOS calls by resident background programs like the DOS
PRINT utility.
Handling the Keyboard
CTask also allows you to circumvent DOS for keyboard input, so
that waiting for the keyboard will not block other tasks from
access to DOS functions. Previous versions of CTask used a "pipe"
to store all keyboard input. Starting with version 1.2, CTask
uses a "flag" to signal that keyboard input might be available. A
"flag" is another form of inter-task communication that just sig-
nals that some event occurred, without passing any specific data.
The reason for using flags in the keyboard handler is compati-
bility to TSR's. If a keyboard interrupt occurs, the interrupt
handler just sets a flag, and passes on the interrupt. The key-
board routines wait for this flag to be set, and then check the
keyboard buffer if a character has arrived. If the keyboard buf-
fer is empty, the flag is again cleared, and the keyboard rou-
tines put the waiting task to sleep again, so the processor is
free to do more interesting things than to loop waiting for the
user to press a key.
Serial I/O and Timeouts
The "pipe" is similar to the mailbox in that you can wait for
items to be sent to a pipe. But unlike mailboxes, pipes use their
own buffer to store the items (which are limited to bytes and
words), so you don't have to allocate mail blocks for each item.
When waiting on the pipe, your task is put to sleep, freeing the
processor. Pipes are used for the serial I/O handler included
with CTask that makes some of the work you've put into your
communications package obsolete. When outputting data to the
serial port via the CTask routines, the data is buffered in a
pipe, and incoming data is also placed in a pipe. All interrupt
handling, and the processing of modem status and XON/XOFF proto-
cols, is done by CTask, so you can concentrate on implementing
the higher level protocols. Since CTask allows all calls that
wait for pipes, mail, and other events, to specify a timeout that
is based on the system tick, you do not have to resort to timed
waiting loops to detect communication line faults. You simply
give a time limit on the wait call, and if that limit expires,
the wait routine will return with an error indication.
Ctask Manual - Version 2.0 - 89-12-21 - Page 4
Priorities
If the protocol you implement requires fast responses to incoming
blocks, you can influence the response of CTask to your comm
task's needs by giving this task a higher priority. CTask allows
65535 different priority levels, and tasks having higher priority
are scheduled before tasks with lower priority. Also, high prio-
rity tasks will get access to mail, pipes, and resources, before
other tasks. It might even be sensible to split the comm task
into two separate tasks, one of high priority that assembles the
incoming bytes into blocks and handles the protocol, and a lower
priority task that reads the received blocks from a mailbox and
stores them on the disk. In extremely time critical applications,
you can even turn off task preemption, so the timer tick will no
longer cause a task switch.
Change to your liking
CTask provides all basic building blocks for implementing
concurrent programs in an easy and comprehensible way. Since
CTask is mainly implemented in C, it may not be the fastest
possible system, but due to the straightforward design which uses
few shortcuts, modifying the sources to suit your needs and taste
can be done without weeks of studying assembler code that
squeezes every microsecond from the processor. CTask is public
domain code, and there are no restrictions on its use. It is
distributed in source form, so you are free to change all aspects
of the package. Multitasking programs, especially in embedded
applications, tend to be very diverse in their needs for specific
constructs. So although CTask is ready to run under DOS, and is
easily adaptable for embedded applications, you should see CTask
more as a starting point for your own thoughts, and as a toolbox
from which you can pick the instruments you need, than as a
finished and fixed block of code you simply plug into your
application.
Ctask Manual - Version 2.0 - 89-12-21 - Page 5
General Notes
What can CTask NOT be used for?
CTask is not intended to provide for multitasking on the command
level of MS-DOS. Although version 1.2 of CTask added the ability
to TSR and spawn other programs, and to communicate between
multiple copies of CTask, a full DOS-process management (which
would have to include keeping track of memory allocation and DOS
process control blocks) is not included. Adding this functio-
nality would not be trivial (although certainly worthwhile).
CTask also is not a true "Real-Time" multitasking system. Due to
the completely dynamic structure of tasks and events, and the
minimal restrictions on what interrupt handlers may do, it is
nearly impossible to calculate a maximum interrupt or task switch
latency. If you have critical timing requirements, you should
consider getting a professional package like AMX. CTask has been
used successfully in embedded control applications, and if your
timing requirements are not that critical, or you're ready to
take up the task of measuring and calculating latencies with your
specific task setup, CTask may be useful even for Real-Time
applications. However, you're more or less on your own in this
field.
And, there is no warranty that CTask does perform without errors,
or does exactly what you or I intended. So CTask should not be
used in applications in which malfunction of routines of this
package would result in damage to property or health of any
person without *very* extensive testing under all kinds of loads.
In using CTask, you do so at your own risk. I have tested CTask
extensively, but with a complex system like CTask, where timing
might make a big difference, you can't be completely sure that
all will work as intended.
What is required to use CTask?
To compile CTask, Microsoft C 5.1 or later, or Turbo C 2.0 or
later are required. Microsoft MASM 5.1 or later, or TASM 1.0 or
later is required for the assembler parts. Conversion to other
compilers is possible if they conform to the new ANSI (draft)
standard. Conversion of the assembler parts to other Assembler
versions requires substitution of the simplified model directives
by explicit segment definitions, and adding the DGROUP to offsets
referencing data.
CTask will add 14k-25k of code to your program, depending on
options and installed drivers. The minimum static data used by
CTask is approximately 4k. Non-DOS versions use less memory.
Ctask Manual - Version 2.0 - 89-12-21 - Page 6
Converting CTask for stand-alone operation requires few changes.
Mainly, the timer interrupt handler (in "tsktim.asm") has to be
rewritten, and the initialization code (in "tskmain.c") may have
to be changed. Changes to other modules (naturally except the
optional hardware drivers) should not be necessary. The "DOS"
configuration flag in tskconf.h can be disabled to eliminate most
system-dependent features of CTask.
Another requirement is a good debugger. If you never before wrote
multitasking applications, you're in for some surprises. The
normal debugging tools (Symdeb, Codeview) are of only limited
use, since they use DOS calls for their I/O, and thus may
conflict with your background tasks. One safety measure is to
first thoroughly test your program with preemption disabled,
possibly inserting some schedule() calls, and only allow task
preemption if you found most major bugs. I personally recommend
Periscope for debugging, since it can be made resident and so is
always available, and because it does not use DOS. Periscope IV
is the most expensive solution, and the best tool you can
imagine, but the less costly versions will also help a lot.
Do I have to pay for using CTask?
No. One reason for writing CTask was to provide a free, no
strings attached, utility, instead of the usual "for personal use
only" restriction. Writing a multitasking application for
personal use only doesn't seem too interesting to me. CTask is
completely free, and there is no restriction on its use. You may
incorporate all or parts of CTask in your programs, and redistri-
bute it in source or binary form by any means. I also do not
restrict the use of CTask in commercial applications. Since
trying to distribute the unmodified CTask for money will only
give you a bad name, you may even do that if you find someone
dumb enough to buy it. Naturally, if you make a bundle from it,
or simply like CTask, I would not reject a donation. However,
this is not required, and it will not give you any special
support.
Ctask Manual - Version 2.0 - 89-12-21 - Page 7
What support can I expect?
I will try my best to eliminate any bugs reported to me, and to
incorporate suggested enhancements and changes. However, my spare
time is limited, so I can not guarantee continued or individual
support. (But since I'm one of the owners of a consulting firm,
you can always hire me to do it...). Please address all reports
or questions to my business address:
Ferrari electronic GmbH
attn: Thomas Wagner
Beusselstrasse 27
D-1000 Berlin 21, West Germany
Phone: (49-30) 396 50 21
Fax: (49-30) 396 80 20
BIX: twagner
UUCP: oeschi@netmbx.UUCP (attn: Thomas Wagner)
But, please, if at all possible, do it in writing. Please do not
phone unless it is absolutely vital (or you have a business
proposal). I like to hear about any applications for CTask, and
if you are visiting Berlin, I also invite you to drop by for a
talk. But I am usually not that happy when I am interrupted in my
paid work by a phone call requesting support for a free product.
I will try to answer all letters and Faxes I receive. However, I
am usually not the fastest in this respect, so please be patient.
The preferred, and the fastest, method to reach me is through
BIX.
BIX (tm) is the BYTE Information Exchange, an electronic confe-
rencing system created by McGraw-Hill, the publishers of the well
renowned BYTE magazine. BIX can be (and is) accessed from all
parts of the world. Although accessing BIX from outside the US
isn't exactly cheap (don't ask me what I have to pay each month),
the wealth of information available there, and the fast and
extensive help the other members can give you on all kinds of
hard- and software problems, makes it worth every Mark, Peseta,
Franc, or Ruble you have to spend. New versions and updates of
CTask will first appear on BIX.
At the time of this writing, I am one of the moderators of the
IBM exchange on BIX, moderating the "ibm.other" conference. I
have created a support topic for CTask, where all suggested
enhancements and changes, plus bug reports, can be posted. Just
join "ibm.other", topic "ctask". You can also report problems of
limited general interest via BIXmail to "twagner". Unless I am
not able to reach the keyboard for some reason, I log on at least
once per day, so you can expect relatively fast responses to your
queries.
Ctask Manual - Version 2.0 - 89-12-21 - Page 8
To get more info on joining BIX, call the BIX Customer Service at
800-227-2983 (U.S. and Canada), or 603-924-7681 (New Hampshire
and outside the U.S.) from 8:30 to 23:00 Eastern Time (-5 GMT).
BIX access currently is $39 for three months (flat fee, no extra
charges for connect time), plus the applicable telecomm charges
(Tymnet in the U.S. and Canada, your local PTT's Packet Net
charges from outside the U.S.). If you're calling from the US,
you can subscribe by dialling BIX direct at 617-861-9767. Hit the
return key, and enter "bix" at the 'login (enter "bix")' prompt.
At the 'Name?' prompt, enter "bix.flatfee". International users
need an account (NUI) with their local packet net. Please enquire
at your post/telecomm office for details. If you already own a
NUI, enter the BIX international network address (NUA),
"310690157800", and enter "bix.flatfee" at the 'Name?' prompt.
About this Release
Since the Beta release of CTask in March 1988, CTask has found
widespread distribution through several channels. I have heard
from some users, and their suggestions have been implemented in
this version as far as possible. Special thanks go to Kent J.
Quirk, Peter Heinrich, Stephen Worthington, Burt Bicksler, Tron
Hvaring, Joe Urso, and Dave Goodwin, for their bug reports,
suggestions, and enhancements. Bug reports also came in from
others, thanks to all who wrote or called. The serial code in
TSKSIO.C was enhanced by S. Worthington.
This release is the long awaited successor to version 1.1.
Although I did announce a "version 1.2", and some users even
received beta copies of it, 1.2 was never officially released.
This release is called 2.0 because of the major changes to the
main CTask data structures, which significantly impact the kernel
routines. Version 1.2 added most of the new features, so you will
see references to 1.2 throughout the manual. If you've been using
(and modifying) 1.1 or a 1.2 pre-release, and the changes to the
queue structure cause trouble for your application, the final
version of 1.2 is available on special request.
Again, please notify me of your CTask application, report bugs,
or suggest changes and enhancements. If I know you're using
CTask, I can notify you of possible new releases (although none
is currently planned), and of possible severe bugs.
Ctask Manual - Version 2.0 - 89-12-21 - Page 9
Multitasking Basics
Tasks
In CTask, a "task" is defined as a (far) C function. The number
of tasks is not limited, and one function may be used for several
tasks. There is little difference between a task function and a
normal function. The usual form of a task function is
void far my_task (farptr arg)
{
one-time initialization code
while (TRUE)
{
processing code
}
}
A task function is (usually) never called directly. Rather, it is
specified in the call to the create_task routine, and started by
start_task. It will then continue to run, sharing the processor
time with all other tasks, until it is "killed" by kill_task.
Returning from the routine will have the same effect as a kill.
The sharing of processor time is accomplished by "preempting" the
tasks. Preemption means that the task is interrupted in the
middle of some statement by a hardware interrupt (usually the
timer), and is *not* immediately restarted when the interrupt
handler returns. Instead, the next task that is able to run is
activated by the "scheduler", with the interrupted task
continuing its duty at some (normally unpredictable) later time.
You can also have multi-tasking without preemption, and CTask
supports this, too, but this requires full cooperation of all
tasks in the system, such that no task continues to run for an
extended period of time without passing control to other tasks by
an explicit scheduling request, or by waiting for an event.
The optional argument to the task function may be used if one
function is to be used for more than one task, for example to
pass a pointer to a static data area for use by this specific
instance of the function.
Events
Tasks alone would be of limited use. If you have several routines
which just do number crunching or sorting or such, making them
into parallel tasks would be sensible only on a multiprocessor
system. Normally, at least some of your tasks will wait for some
outside "event" to happen, be it the user pressing a key, or a
character arriving from the modem. Then there may be tasks which
have to wait until another task finishes processing on some piece
of data before they can continue. For this synchronization, there
are a number of constructs in CTask, which I summarize under the
Ctask Manual - Version 2.0 - 89-12-21 - Page 10
name "event". Common to all events are the operations of waiting
for an event to happen, and signalling that the event has
happened. Using a CTask event is much more efficient than looping
while waiting on some shared data location to change state, and
also eliminates concurrency problems inherent in such a simple
approach. Tasks waiting for an event are taken off the scheduler
queue, so they no longer use processor time.
Reentrancy
One of the biggest problem with multitasking in general, and C on
the PC in particular, is reentrancy. Reentrancy means that you
can use a routine, be it you own, or one of the C run-time
library, from different tasks at the same time. When writing your
own code, you can easily avoid problems, but when using the run-
time library routines, you often can only guess if the routines
are reentrant or not.
A routine is NOT reentrant if it modifies static data. This can
be illustrated by the following nonsense example:
int non_reentrant (int val)
{ static int temp;
temp = val;
return temp * 2;
}
Now take two tasks, which call this routine. Task1 calls it with
val=3, Task2 with val=7. What will be the return value for both
tasks? You never know. There are three possible outcomes:
1) The tasks execute sequentially. Task1 will get 6, and Task2
14 as a result. This is what one normally expects.
2) Task1 runs up to "temp = val", then is interrupted by the
timer. Task2 executes, and gets 14 as result. Then Task1
continues. Return for Task1 is 14.
3) Task2 runs up to "temp = val", then is interrupted by the
timer. Task1 executes, and gets 6 as result. Then Task2
continues. Return for Task2 is 6.
add to this the effects of optimization, and a loop, and the
outcome is completely random.
Most routines in the C library will not explicitly do something
like this, but all functions that
- do file I/O (read, write, printf, scanf, etc.) or
- change memory allocation (malloc, free, etc.)
Ctask Manual - Version 2.0 - 89-12-21 - Page 11
have to use some static data to do buffering, or to store the
chain of memory blocks in. Interrupting such an operation may
have disastrous effects. The most devilish aspect of non-
reentrancy is that the effects are unpredictable. Your program
may run 1000 times without any error, and then on the 1001th time
crash the system so completely that only the big red switch will
help.
So what can you do about it? A lot. There are several ways to
protect "critical regions" from being entered in parallel. The
most simple method is to disable interrupts. This, however,
should be used only for *very* short periods of time, and not for
rou-tines that might themselves re-enable them (like file-I/O). A
better method is to temporarily disable task preemption. The
routines tsk_dis_preempt and tsk_ena_preempt allow this form of
short-term task switch disable. Interrupts may still be
processed, but tasks will not be preempted. The best way is to
use a "resource". A resource is a special kind of event, which
only one task can possess at a time. Requesting the resource
before entering the routine, and releasing it afterwards, will
protect you from any other task simultaneously entering the
critical region (assuming that this task also requests the
resource).
It is also reasonably safe to use file-I/O without protection if
it goes to different files. The C file control blocks for
different files are distinct, so there will be no conflict
between tasks. Since DOS access is automatically protected by
CTask, concurrent file I/O is possible.
What you may NEVER do is to use a non-reentrant routine that is
not protected by an interrupt disable from an interrupt handler.
An interrupt handler is not a task, and so can not safely request
a resource or disable task preemption. This is the reason why the
CTask routines generally disable interrupts before manipulating
the internal queues rather than only disabling task preemption.
Deadlocks
One thing to watch out for when using resources or similar event
mechanisms is not to get into a situation where Task 1 waits for
a resource that Task 2 has requested, while at the same time Task
2 waits for a resource that Task 1 already has. This situation is
called a deadlock (or, more picturesque, deadly embrace), and it
can only be resolved with outside help (e.g. waking up one of the
tasks forcibly). To illustrate, consider the following example:
Ctask Manual - Version 2.0 - 89-12-21 - Page 12
void far task_1 ()
{
...
request_resource (&rsc1, 0L);
request_resource (&rsc2, 0L);
...
}
void far task_2 ()
{
...
request_resource (&rsc2, 0L);
request_resource (&rsc1, 0L);
...
}
Since interrupts are always enabled on return from a task switch,
even if the statements are enclosed in a critical region, there
is no guarantee that the request_resource calls will be executed
without interruption. In this example, the problem is obvious,
but in a more complex application, where resource requests or
other waits might be buried in some nested routine, you should
watch out for similar situations. One way to avoid problems would
be in this example to change task_2 to
void far task_2 ()
{
int again;
...
do {
request_resource (&rsc2, 0L);
if (again = c_request_resource (&rsc1))
{
release_resource (&rsc2);
delay (2L);
}
} while (again);
...
}
Note that this is only one of many possible approaches, and that
this approach favors task_1 over task_2.
You should also take care not to kill tasks that currently own a
resource. CTask will not detect this, and the resource will never
be freed.
Ctask Manual - Version 2.0 - 89-12-21 - Page 13
Using CTask
CTask comes archived with both source and binaries. The binary
version is compiled in the large model, but since the precompiled
kernel routines don't use any functions from the C library, you
can use all functions in small or other model programs (except
Turbo C's Tiny and Huge models). The include files provided
specify all model dependencies, so you don't have to use the
large model for your application, but always remember to include
"tsk.h" for the type definitions and function prototypes.
The C source files will work without changes for both Microsoft
and Turbo C. The library files are not compatible, so use
"ctaskms.lib" for Microsoft, and "ctasktc.lib" for Turbo C.
In the distributed configuration (i.e. dynamic allocation of
control blocks enabled), the file TSKALLOC.C must be added to the
library or separately linked after compiling it *in the same
model as the main program*. This file uses C-library routines,
and thus must match the main program's memory model. The same
goes for TSKSNAP.C, an optional snapshot-dump utility, and
CONOUT.C, the sample console output handler. The provided
"ctsupms.lib" and "ctsuptc.lib" files have been compiled in the
large model, and may only be used with large model programs.
Turbo C's huge model uses a different segment setup for data
segments. This requires using a special data segment for all
CTask data, and compilation of the CTask kernel in huge model.
For the assembler files, the symbol TC_HUGE must be defined
during assembly, the C files must be compiled with the Data-
Segment and BSS-Segment naming options. Make-files for Turbo C
Huge model are included.
Configuration Options
The file TSKCONF.H contains a number of #define's that allow you
to configure some CTask features. In general, you should
recompile all of CTask when changing one of the flags. You must
also take care to modify the corresponding equate in the
assembler-include TSK.MAC.
The entries are
TSK_DYNAMIC
If 1, you can let CTask dynamically create task and event
control blocks, task stacks, and pipe buffers, by passing
NULL as the block address. Since this requires the C runtime
allocation calls, it is not suitable for non-DOS
applications (except if you provide your own memory
allocation routines).
Ctask Manual - Version 2.0 - 89-12-21 - Page 14
This option is normally enabled (1).
TSK_NAMEPAR
If 1, all create_xxx calls accept an additional parameter,
the name of the created control block. This name should con-
tain only printeable characters, and should not be longer
than 8 characters plus the zero terminator. This option is
used together with TSK_NAMED (see below).
This option is normally enabled (1).
It must be enabled for DOS.
TSK_NAMED
If 1, all control blocks (except timer control blocks) are
named and linked on creation. This allows the snapshot dump
routine to display the system state. TSK_NAMEPAR must be
defined for this option to work. Since it may be desirable
to switch off the handling of the names after the debugging
phase, without having to change all create_xxx calls to omit
the name parameter, the name parameter is ignored if
TSK_NAMED is undefined, but TSK_NAMEPAR is defined.
This option is normally enabled (1). It must be enabled for
DOS.
CLOCK_MSEC
If 1, all timeouts are specified in milliseconds instead of
timer ticks. This allows programming of delays and timeouts
independent of the speedup-parameter or the system tick
rate. Since timeout calculations use floating point
operations if this option is enabled, a floating point
library is needed. This precludes model independence of the
CTask kernel.
This option is normally disabled (0).
PRI_TIMER
Specifies the priority of the timer task. Normally the timer
task should have a higher priority than any other task in
the system.
The value is normally 0xf000.
PRI_STD
This value may be used in your programs as the standard
priority of user tasks. Its value is arbitrary. It is not
used in the kernel except for the definition of PRI_INT9
(see below).
The value is normally 100 (decimal).
PRI_INT8
Determines the priority of the "int8"-task. This task chains
Ctask Manual - Version 2.0 - 89-12-21 - Page 15
to the previous interrupt vector for the sytem timer tick,
and thus may activate resident TSR's. Since TSR's normally
use polling when accessing the keyboard and other devices,
the priority of the int8-task should be equal to or lower
than normal user-defined tasks to allow your program to con-
tinue to run while the TSR is active. You can tune this
value so that some tasks are blocked by the TSR to avoid
trashing the screen.
The value is normally PRI_STD.
IBM
DOS
Both IBM and DOS are more or less of informative value only,
to point out those areas in the kernel that need attention
when converting to non-IBM or non-DOS environments.
Disabling one or both of this options requires the
substitution of your own routines for installation, timer,
and keyboard handling.
Both options must normally be enabled (1).
GROUPS
If enabled, task groups, i.e. multiple invocations of CTask,
are supported.
This option is normally enabled (1).
It should be enabled for DOS.
SINGLE_DATA
If enabled, only a single global data block is used. This
speeds up processing, but prohibits linkage of multiple
groups. Useful for dedicated systems, and also if your
program is known to never interact with another copy of
CTask. Should be left 0 if you're not so sure, must be 0 if
GROUPS is enabled.
AT_BIOS
If enabled, the AT BIOS wait/post handler is installed. May
be disabled when compatibility problems arise.
This option is normally enabled (1).
You can also disable installation of this handler with an
install flag in the install_tasker call.
Memory Allocation
TSKALLOC.C is needed if TSK_DYNAMIC is enabled in tskconf.h to
handle the allocation and free calls. If you want to use dynamic
allocation in your own tasks, you should also use the functions
tsk_alloc and tsk_free to avoid the reentrancy problems mentioned
Ctask Manual - Version 2.0 - 89-12-21 - Page 16
in the introduction. If you should need other forms of alloc
(like calloc) the recommended way is to add those functions to
TSKALLOC.C, requesting the resource alloc_resource before calling
the C memory-allocation function.
Joe Urso provided the following tip on how to force all memory
allocation to pass through the resource handling functions:
"I have found that some MS Library functions call the allocate
and free library functions, namely the first call to fread(),
fopen(), and fclose().
To fix this situation I have
- extracted FMALLOC from the llibce library,
- renamed the functions within that object (via the Norton
Utilities) to the upper case spelling of the functions
(FREE, MALLOC, _FFREE and _FMALLOC) and replaced the file in
the lib. I also did the same for object EXPAND because of
the function REALLOC.
- then modified TSKALLOC.C to contain the lower case spelling
of malloc, free, and expand. These functions aquire the
alloc resource, then call the upper case spelling of the
name.
At link time I now link with /NOIGNORECASE and /NODEFAULTLIB."
Snapshot
TSKSNAP.C is only needed if you want to include the snapshot dump
into your program. Note that you can *not* use snapshot if you
disable TSK_NAMED in tskconf.h.
Task Stacks
When compiling your application, turn stack checking off. The
standard stack check is of little use with task stacks, and may
interfere with CTask's operation. The stack area for tasks should
be allocated on the main program's stack, not in the static or
heap data space. The reason for this is that some of the C
library routines check for stack overflow regardless of your
compile-time switches, and will crash your application if you use
stacks outside the normal stack. The stack allocation parameter
with LINK (MS-C), or the _stacksize variable (Turbo C) have to be
increased to reflect the additional stack space. When calculating
task stack sizes, keep in mind that library routines (esp.
printf) allocate a lot of space on the stack for temporary
variables. A minimum of 1k for tasks using library routines is
recommended, 2k puts you on the safe side. Tasks not using C
Ctask Manual - Version 2.0 - 89-12-21 - Page 17
library routines may use a smaller stack, about 256 bytes at a
minimum, plus space for any local variables and nested routines.
Then add up all task stacks, and add space for the main task (the
function calling install_tasker), with this size also dependent
on what you will do in the main task while CTask is active.
Stacks for tasks that do not use C library routines may be
allocated anywhere.
Drivers
The keyboard and DOS handlers are always installed with CTask.
Using the serial I/O and printer drivers is optional, so you have
to install them separately, but only *after* installing CTask.
When using the serial driver, include "sio.h" in your modules,
when using the printer driver, include "prt.h".
Another driver that is automatically installed is the BIOS
wait/post handler for the IBM AT. The AT BIOS contains some
multitasking hooks to avoid busy waiting in the BIOS. If you
experience problems with disk accesses or printer output through
BIOS (not through the CTask printer driver), you should disable
installation of this driver by setting AT_BIOS to zero in
tskconf.h, or not specifying the IFL_INT15 flag in
install_tasker. Normally, no problems should arise with this
driver even in XT type machines.
Things to remember
Remember that tasks are not automatically started after creation.
Use start_task to allow a created task to run.
Always use create_xxx on resources, pipes, etc. Using the event
routines without doing so will have unpredictable results.
Before exiting the program, all installed drivers and CTask
should be explicitly removed. Although the DOS handler traps the
terminate call and automatically calls remove_tasker, you should
make sure that all tasks are completed properly, and call
remove_tasker yourself.
Deleting events before exiting the program is not mandatory, but
recommended to kill all tasks waiting for the event. You should
be careful not to kill tasks while they are active in DOS. The
kill_task routine should be reserved for fatal error handling.
The best way is to let the tasks kill themselves depending on
some global variable or event. If a task is killed while waiting
for input in DOS, DOS operation may be severely impaired. If you
use the C console input routines, make sure that the task returns
from DOS before it is killed, if necessary by requesting the user
to press a key.
Ctask Manual - Version 2.0 - 89-12-21 - Page 18
Priority Handling
CTask provides for prioritized task execution and event
processing. This means that a task that has a higher priority
will be run before any other tasks having lower priority. Also, a
higher priority task will gain access to resources, counters,
pipes, and mail, before lower priority tasks. With fixed
priorities, this means that a high priority task can monopolize
CPU time, even if it calls schedule() explicitly. Variable
priority increases each eligible task's priority by one on each
scheduler call, so that lower priority tasks will slowly rise to
the head of the queue until they get executed. The priority will
be reset to the initial priority when a task is run.
Since variable priority increases processing time in the critical
region of the scheduler, it is not recommended for systems in
which a larger number of tasks is expected to be eligible
simultaneously.
Usually, all tasks in a system should have the same priority,
with only very few exceptions for non-critical background
processing (low priority) or very time-critical tasks (high
priority). High priority tasks should be written in such a way
that they either reduce their priority when processing is
completed, or that they wait for an event. Busy waiting in a high
priority task will severely impair system operation with variable
priority enabled, and will stop the system until the task is
placed in a waiting state with fixed priority.
The (automatically created) main task is started with the highest
possible priority below the timer task, so that it can process
all initialisations before other tasks start running. To allow
the system to operate, the priority of the main task must be
reduced, or the main task must wait for an event or a timeout.
Ctask Manual - Version 2.0 - 89-12-21 - Page 19
Multitasking and DOS
CTask includes (and automatically installs) a routine which traps
all DOS calls, and makes sure that no two tasks simultaneously
enter DOS. This is accomplished using the resource mechanism,
with special provisions for the limited multitasking capabilities
provided by DOS. There are a few calls, namely those with
function codes <= 0x0c, which allow functions with codes > 0x0c
to be executed while DOS is waiting for an external device
(generally the keyboard) to get ready.
This, however, limits the use of some C library functions, namely
scanf and fread, for console input. Both these functions use
handle input, and thus can not be interrupted. When writing
routines for handling user input, keyboard read functions should
either use the low-level calls getch and gets, or, better yet,
the direct entries into the keyboard handler, t_read_key and
t_keyhit, and then process the string with sscanf if desired.
The keyboard handler (contained in tskkbd.asm) traps all keyboard
interrupts, setting a flag when a key is hit. If a task reads
from the keyboard, it is automatically made waiting if there is
no key to read. Using getch and gets is less desirable since they
use polling instead of event waiting, and thus degrade system
performance. Also, the t_read_key and t_keyhit functions do not
use DOS, so DOS functions <= 0C can be executed concurrently.
The BIOS interrupts 10 (Video), 13 (Disk), and 16 (Keyboard) are
protected by CTask. Using other BIOS interrupts directly should
generally be avoided, unless you are absolutely sure they will
not be used by other routines via DOS, or you provide your own
critical region handling. Protection of INT 10 is optional, and
should only be used if you expect to use BIOS level video output
in parallel to other tasks. Since INT 10 calls occur extremely
frequent in most programs, the overhead for reserving and
releasing the resource can slow down the system noticeably.
Starting with version 1.2, the DOS handling has been extended to
automatically save and restore the DOS variable context on a task
switch. This is done by directly saving the critical variables of
the DOS data area in the task control block. An undocumented DOS
call is used to determine location and length of this area. NOTE
that this call is not available in DOS versions prior to 3.1, and
may not be available in MS-DOS clones or emulators. This context
save allows multiple DOS applications to run concurrently.
The DOS access module has been tested to work with PC-DOS and MS-
DOS versions 3.20, 3.30, 4.00, and 4.01. There were also no
problems with the DOS PRINT program running in the background.
Special provisions are built into the DOS module to detect
background DOS calls. Using Sidekick also hasn't lead to any
problems, although you may trash Sidekick's screen display by
Ctask Manual - Version 2.0 - 89-12-21 - Page 20
writing to the screen while Sidekick is active (note that your
application *continues* to run while Sidekick or other pop-ups
are active). I can not guarantee complete compatibility with all
background and pop-up programs under all versions of DOS.
Specifically, DOS versions prior to 3.1 have significant problems
with multitasking, and do not support the variable save call
necessary for full DOS multitasking. Upgrading to a newer DOS
version is recommended if you are still using DOS < 3.2 (DOS 3.1
has some bugs in other areas).
Also starting with version 1.2, CTask is now compatible with MS-
Windows. It has been tested to work with Windows 286 1.2 and 2.1,
but the mechanism used should work with other non-preemptive
versions, too. CTask can be installed as a background TSR before
starting Windows, or you may spawn Windows from CTask.
Windows/386 is a special virtual-mode operating system and can
generally not be combined with other multitaskers.
Preemptive multitaskers like DesqView will most likely not
coexist peacefully with CTask.
Critical errors and Control C occurring while concurrent DOS
access takes place may be fatal. Using the ctrlbrk() and
harderr() functions of Turbo C, or their equivalents in MS C, to
trap critical errors and Control C is highly recommended. Error
handlers should be installed before CTask is installed.
Spawning and CTask TSR's
Starting with Version 1.2, CTask programs can now spawn other
programs, and Terminate and Stay Resident (TSR). The DOS context
switch, plus the saving and restoring of interrupts 21-24, on
every task switch, makes it possible to have multiple, nearly
completely independent, programs running under DOS. But please
note that CTask is not intended as a replacement for true DOS-
multitaskers like DesqView. Implementation of such a system would
surely be possible, using CTask as a base, but would require
keeping track of all DOS control blocks. This is left as an
exercise to the reader. The new features are intended to allow
you to easily write background data aquisition or communication
modules that run in parallel to other DOS applications.
Since the spawned program might itself be a CTask program,
Version 1.2 automatically checks for the presence of a background
copy of CTask. If one is found, the secondary invocation will
link to the global data of the first CTask copy, making it
possible to easily communicate between independent instances of
CTask programs. The naming mechanism has been slightly changed to
make it easier to find a task or data structure by name.
For example, you could write a communications program in two
separate parts. One part, the resident part, contains interrupt
Ctask Manual - Version 2.0 - 89-12-21 - Page 21
handlers and the protocol routines. The other part, the transient
portion, contains the driving program, with editor, menus, script
language and so on. If the resident part is started, it first
spawns the transient part, which handles setup, and starts
communications. Both parts can communicate using all CTask
mechanisms, i.e. pipes, buffers, mailboxes etc., with the
transient part obtaining the pointers to the CTask structures
defined in the resident part via the structure name. Now if an
up- or download is started, the transient part can exit to DOS,
and the resident part can spawn a copy of command.com. Voila -
background up/download with minimum hassle.
Task Groups
Each invocation of the CTask kernel (i.e. each call of the
install_tasker routine) creates a new "Task Group". This group
chains all tasks and other control structures created by this
program together, so they can be killed if the group terminates
(through a DOS call or the remove_tasker routine). Although it is
recommended to kill all structures before terminating, there are
a number of situations where this is hard to control under DOS.
So CTask tries to take a few safety measures to make sure the
system does not crash due to pointers chaining into nowhere.
But please note that care should be taken not to "pull the rug
under a group's feet". CTask will attempt to clean up memory when
a group terminates. However, if that group has spawned another
non-CTask program, CTask doesn't know about it, so an orphan will
be left in memory, and you might even manage to get the system
into an unstable state.
And, there are a few restrictions. Although it is possible to
create multiple groups running, and terminating, independently in
parallel, those groups must be created by separate tasks from one
"home group". If one task creates a group, and the same task, or
another task from this group, creates a second, then it is not
possible to terminate the first group without also killing the
second. You can view this like a tree, where you can cut off
branches, but all sub-branches of that branch will come down with
it. The root of that tree, the primary invocation of CTask, can
not be killed without killing the whole tree.
Also, it is mandatory that you at least try to call remove_tasker
on termination. CTask can trap a program's termination, but this
happens at a time when all the program's memory has already been
released by DOS. This can lead to extremely dangerous situations
if other tasks, or TSR's, allocate and modify this memory before
CTask finishes it's cleanup. CTask will stop preemption while it
is removing the group, but it still is definitely not recommended
to rely on this mechanism.
Ctask Manual - Version 2.0 - 89-12-21 - Page 22
A sample for a task group structure follows:
-Root-
home
level
branch -----+
^ ^ |
| | |
| | v
-Group1- | | -Group2-
home ------+ +----- home
level <------------- level
branch -----+ branch
^ ^ |
| | |
| | v
-GroupA- | | -GroupB-
home -----+ +----- home
level <------------ level
branch branch
In this sample, Groups 1 and 2 were created by different tasks in
the Root group, Groups A and B by different tasks in Group 1.
Three pointers are used in chaining groups. The 'branch' points
to the first element in an unordered list of task groups on the
same "branch level" one level "up" the tree. Those groups are
chained by the 'level' pointer. To allow finding the home group
when going "down" the tree (towards the root), all groups on the
same level point there through the 'home' pointer. In this
sample, you could kill Group2 without affecting Group1 or it's
subgroups. Killing Group1 will terminate GroupA and GroupB.
Ctask Manual - Version 2.0 - 89-12-21 - Page 23
How does CTask work
Queues
CTask uses priority queues for most of its functions. A priority
queue differs from the usual FIFO (first in, first out) queues in
the insertion of elements into the queue. Rather than just
placing new elements after the last queue member, the priority of
the task determines its place in the queue. A task is enqueued
after all queue members with higher or equal priority, but before
any member with lower priority. Removal from the queue takes
place at the first element. These queues are used with all
operations in which a task is made waiting, be it waiting to get
run, or waiting for an event. The advantage of this is the
simplicity of implementing priorities. Other schemes might rely
on arrays of queues, or searching through lists, to determine the
highest priority waiting task. With priority queues, the most
time critical function, scheduling, is very fast, since it can
simply take the first task from the queue of eligible functions.
The disadvantage is the time required to step through all greater
or equal priority members of a queue to find the right place to
insert the task. But since the number of tasks simultaneously
enqueued in the same queue is normally relatively small with most
multitasking systems, this disadvantage is more than offset by
the simple, and thus easy to implement efficiently, scheme.
Version 2.0 generalizes the queueing scheme, and uses doubly
linked lists for all internal chains (except the ticker chain).
The introduction of the "yield" function, which inserts a task at
the end of the eligible queue, but resets the priority, mandated
searching through the queue from the back end. The previous
scheme, which walked the queue from the front end, could lead to
task starvation in a system with polling tasks when yielding.
Also, the timer algorithm was changed such that the timer task
didn't have to step through all timeout elements, but rather to
only decrement the first queue element ticker. This is
implemented by storing the timeout as a tick difference to the
next queue element rather than as an absolute value. Doubly
linked lists make insertion and removal of elements at arbitrary
points very simple, and eliminate the time needed to find an
element in a queue.
To alleviate the additional overhead associated with managing
dual pointers, the main queueing routines were coded in
assembler. Especially when operating with multiple far pointers,
the code generated by the C compilers even with maximum
optimization is not that impressive. The corresponding C code is
included as a reference, and to aid porting.
Ctask Manual - Version 2.0 - 89-12-21 - Page 24
The Scheduler
In a CTask system, there is at least one essential queue: the
eligible queue, which contains all tasks waiting to run. Each
time the scheduler is invoked, it will first save all processor
registers in the current task control block, and then enqueue the
current task into the appropriate queue. Normally, this will be
the eligible queue. Then the first element in the eligible queue
is removed from the queue, the stack is switched to the stack of
this task, and processor registers are restored from the new TCB,
returning to the point where the new task was interrupted. If the
eligible queue is empty the scheduler will loop with interrupts
enabled. The queue head pointer of the task control block of the
current running task determines what will happen to the task when
the scheduler is invoked. If it points to an event control block,
the task will be enqueued as waiting for this event. If it is
NULL, the task will not be enqueued in any queue, effectively
stopping it. If it points to the eligible queue, the task will be
enqueued there. In any case, the scheduler does not care what
queue the task is to be enqueued in, since all queues use the
same priority based ordering. If a task is no longer in the
eligible queue, it can only be returned to this queue by an
external event.
Events
External events can take several forms, which are relatively
similar on the inside. All CTask events use a control block, with
at least one queue for tasks waiting for the event. Although it
would have been possible to summarize all events under a global
structure, this approach was not used in CTask, the main reasons
being execution speed and ease of use. So if you scan through the
source files for CTask events, you will see many very similar
routines, which only differ in the types of data and the names of
queues they process. In a class based language like C++, one
would certainly define all events as instances or derivations of
one class. In plain C, the necessary type casting, and the
different handling of certain cases, would clobber the code to
the point of illegibility.
Resources
The event types "resource", "flag", and "counter" differ only in
the kind of wait operations and the handling of the state
variable. The resource is mainly for use in interlocking critical
regions of code, to protect access to non-reentrant resources.
Only one task can "own" a resource, all other tasks requesting
the resource are delayed until the owning task releases it. For
added protection, CTask stores the task control block address of
the current owner of the resource in the resource control block,
so no other task can erroneously release it.
Ctask Manual - Version 2.0 - 89-12-21 - Page 25
Flags
Flags, on the other hand, can be set and cleared by any task, and
also by interrupt handlers. Tasks can wait on either state of the
flag, and all tasks waiting for a state are simultaneously
activated when the flag is changed to this state. This makes
flags suitable for use as a global signalling mechanism.
Counters
Counters are a variation on the flag concept. A counter can be
incremented and cleared by any task, and by interrupt handlers.
Tasks can wait for a counter to be zero or nonzero. Like flags,
all tasks waiting for the zero state are activated
simultaneously. But unlike flags, only the first task waiting for
the nonzero state of a counter will be activated on incrementing
the counter, and the counter will be automatically decremented by
one. The counter is used inside CTask to handle timer interrupts.
The timer counter will be incremented on each timer tick,
activating the timer task. If for any reason the timer task is
unable to complete its run until the next tick, this tick will
not be lost, since the counter is incremented, and the timer task
will continue to run the next time it calls the counter wait
request.
Mailboxes and Pipes
The "mailbox" and "pipe" events can be used for inter-task
communication, and for the communication between interrupt
handlers and tasks.
Mailboxes can hold an unlimited number of mail blocks. Mail
blocks have no fixed structure, but the first doubleword in each
block passed to a mailbox routine is used as a chain pointer.
Mail blocks are chained into a mailbox in FIFO order. Tasks can
wait for mail to arrive, or can conditionally read mail if a
block is available. Tasks and interrupt handlers can write blocks
to a mailbox. Since mailboxes don't need any copying of data,
they are suited for high speed exchange of larger amounts of data
between tasks. The disadvantage of sending mail this way is that
you have to make sure that a mail block is not re-used before it
has been read out of the box and processed by the receiving task.
When exchanging fixed length messages, you can build a free mail
block chain by using a mailbox to hold the available blocks.
Buffered message exchange is possible using pipes. When creating
a pipe, you specify a buffer area and its length, and the pipe
routines will buffer all data written to the pipe in this area.
This implies that writing to a pipe may cause a task to be
delayed until there is space available in the pipe. To allow
interrupt handlers to write to pipes, there is a conditional
Ctask Manual - Version 2.0 - 89-12-21 - Page 26
write request, which will simply return if the pipe is full.
Tasks can wait for data to arrive in a pipe, and for the pipe to
be emptied. A conditional read request is also provided. The
disadvantage of pipes is that they are slightly slower than
mailboxes due to the necessary copying, and that you can only
place word or byte sized items in a pipe. When there is more than
one reader or writer task, you can not rely on the bytes in a
pipe being in any specific order. A "buffer" construct is
provided in CTask that expands pipes to allow arbitrary length
messages to be written and read. This is implemented using a
resource for reading and writing to the pipe associated with the
buffer, so the message is always guaranteed to be written and
read in one piece. But since resources can not be used in
interrupt handlers, using such buffers is not allowed from
interrupts.
Serial and Printer Drivers
There are a number of routines included in the CTask package for
PC-specific tasks. Although they will be of limited use for
embed-ded applications, studying the serial I/O and printer
interface routines will give you some hints on how to use the
CTask kernel for implementing your own device drivers. For PC
based appli-cations, the routines should be usable with no or
little changes for implementing complete communications packages.
The serial I/O handler uses interrupts for both input and output
of data, and supports both XON/XOFF and RTS/CTS handshake
methods. The modem inputs can selectively be enabled to control
data transmission. Pipes are used for transmit and receive data,
with the buffer and its size specified on initialization. Both
COM1 and COM2 can be active simultaneously, and other ports can
be supported by editing a table in the source code or defining
ports on-line. Since CTask allows the access to all events in
interrupt handlers, writing interrupt based I/O drivers is
relatively simple. On receiving a character, the character and
any associated errors are placed in the receive pipe, and the
transmit pipe is read on transmit ready interrupt. Note that
conditional reads and writes have to be used in the interrupt
handler, since you can't safely delay an interrupt handler.
The printer output driver supports both polling and interrupt
output. Again, a pipe is used for the output characters.
However, since polling is supported, and because of the somewhat
unreliable interrupt structure of the printer ports, a driver
task is required. This task is automatically created on
installing the driver. The task first waits on the pipe for a
character to be written to the printer. When polling is enabled,
it tries to output the character directly, using a short busy-
waiting loop. So as not to load the system too heavily by the
polling, the task will yield if it can't output the character
after a small number of loops. When interrupts are enabled, the
Ctask Manual - Version 2.0 - 89-12-21 - Page 27
process is essentially the same at the start, but after the
character is written to the port, the task will wait on a flag to
be set. The interrupt handler will set the flag on interrupt,
enabling the task to get the next character from the pipe. Since
interrupts are unreliable with most printers due to the usually
very short pulses on the acknowledge line, the task uses a
timeout on the flag wait, so it does not hang if an interrupt is
missed. Studying the printer driver will also give you an idea
what the optional parameter on task creation can be used for. In
the printer driver, this parameter is used to pass the address of
the printer control block, which contains the pipe, the flag, and
the hardware info, to the task. This allows the printer driver to
be simultaneously installed for any number of printers without
having to write separate printer tasks.
Ctask Manual - Version 2.0 - 89-12-21 - Page 28
CTask Data Types
Note that you do not have to know the innards of the structures.
All structure fields are filled by the "create_xxx" routines and
modified by the CTask functions. You should NEVER modify a field
in one of the structures directly. The structures are explained
here shortly only for those wanting to modify the routines.
NOTE: When modifying CTask structures, take care to modify the
equivalent definitions in the assembler include file "tsk.mac".
Some of the assembler routines have to use field offsets into
pointers, so having different offsets in C and assembler will
crash the system.
If you only want to use the routines, you should simply include
the file "tsk.h" in your source, and define variables of the
types
tcb - for task control blocks
tcbptr - for far pointers to tcbs
flag - for flag events
flagptr - for far pointers to flags
resource - for resource events
resourceptr - for far pointers to resources
counter - for counter events
counterptr - for far pointers to counters
mailbox - for mailbox events
mailboxptr - for far pointers to mailboxes
pipe - for pipe events
pipeptr - for far pointers to pipes
wpipe - for word pipe events
wpipeptr - for far pointers to word pipes
buffer - for buffer events
bufferptr - for far pointers to buffers
tlink - for timeout and watch control blocks
tlinkptr - for far pointers to timeout control blocks
namerec - for control block names
nameptr - for name pointers
without caring what's behind them.
Ctask Manual - Version 2.0 - 89-12-21 - Page 29
Additionally, you may use the types
byte - for unsigned characters
word - for unsigned short integers
dword - for unsigned long integers
funcptr - for pointers to task functions
farptr - for far pointers to anything
byteptr - for far pointers to byte arrays
wordptr - for far pointers to word arrays
in defining or typecasting items to be passed as parameters to
CTask functions.
Typedefs used for simplified type specifications
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;
typedef void (cdecl far *funcptr)();
typedef void far *farptr;
typedef byte far *byteptr;
typedef word far *wordptr;
Error return values for event wait functions
#define TTIMEOUT ((farptr) -1L)
#define TIMEOUT (-1)
-1 is returned if a timeout occurred while waiting for
an event.
#define TWAKE ((farptr) -2L)
#define WAKE (-2)
-2 is returned if the task was waked up while waiting
for an event.
#define TWATCH ((farptr) -3L)
#define WATCH (-3)
-3 is returned if the task was waked up during a wait
by a watchpoint action.
Ctask Manual - Version 2.0 - 89-12-21 - Page 30
Queues
The 'queue' structure is a dual link for linking task control
blocks and timer blocks. The first three fields are used both for
the queue head, and for elements to be inserted in a queue.
CAUTION: Do not change the order of the first three fields in
either queue or queue_head! Those two structures are used
interchangeably in various places in the CTask kernel. Changing
one of them would have catastrophic results.
The queue element ("queue") contains the forward and backward
pointer, a structure kind field, plus the priority (when used in
a TCB) or the timeout value (when used in a tlink structure). The
kind field is used to identify the type of structure the queue
element is a part of, and also to identify the "end" of the
queue. To simplify insertion and removal, all queues are circular
in nature, i.e. there is no NULL-pointer at the head or tail. The
queue head, and thus the end of a queue, is identified by a zero
kind field.
typedef struct {
word prior;
word ini_prior;
} qelem_pri;
typedef struct queue_rec far *queptr;
typedef struct queue_rec {
queptr volatile next;
queptr volatile prev;
byte kind;
union {
qelem_pri pri;
dword ticks;
} el;
} queue;
The queue head ("queue_head") also contains the forward and
backward pointer, and the structure kind field. The kind field is
0 in true queue heads. The same structure is also used to chain
name blocks, since those do not need the priority/tick field
present in normal queue elements.
typedef struct {
queptr volatile first;
queptr volatile last;
byte kind;
} queue_head;
typedef queue_head far *queheadptr;
Ctask Manual - Version 2.0 - 89-12-21 - Page 31
The timer/watch control block
The timer control block is included in every task control block.
It may also be created as a separate entity to specify special
actions to be executed after or every n timer ticks. It may also
be used to specify memory or port locations to be watched for a
change or a certain value on every timer tick.
The "link" field links the active structures into the timer
queue.
"strucp" points to the structure to be acted upon, "kind"
specifies the kind of structure:
#define TKIND_TASK 1
Strucp points to the task control block of which the
element is a member. The task will be awakened when the
timeout expires.
#define TKIND_WAKE 2
Strucp points to a task control block. Otherwise same
as TKIND_TASK.
#define TKIND_PROC 3
Strucp contains the address of a function to be called
on timeout.
#define TKIND_FLAG 4
Strucp points to a flag that should be set on timeout.
#define TKIND_COUNTER 5
Strucp points to a counter that should be increased on
timeout.
The timeout structure is used for both timeouts and memory/port
watchpoints. The union "elem" contains either the tick reload value,
or the specs for the memory or port location to watch. "elkind"
specifies the kind of structure used in the upper nibble:
#define TELEM_TIMER 0x10
This is a timeout element.
#define TELEM_MEM 0x20
This is a memory watch element.
Ctask Manual - Version 2.0 - 89-12-21 - Page 32
#define TELEM_PORT 0x30
This is a port watch element.
The lower nibble of "elkind" is used for watchpoints, it
specifies the comparison operation:
#define TCMP_EQ 1
Activate watch if value read is equal to "compare".
#define TCMP_NE 2
Activate watch if value read is not equal to "compare".
#define TCMP_GE 3
Activate watch if value read is >= "compare"
(unsigned).
#define TCMP_LE 4
Activate watch if value read is <= "compare"
(unsigned).
#define TCMP_GES 5
Activate watch if value read is >= "compare" (signed).
#define TCMP_LES 6
Activate watch if value read is <= "compare" (signed).
#define TCMP_CHG 7
Activate watch if value read is not equal to "compare".
Additionally, store the value read into the "compare"
field. Only useful for continuous watchpoints.
For timeouts, "elem.time" contains the "reload" value used for
repetitive timeout actions to reload the original timeout value.
The actual timeout value is part of the link structure.
For port watches, "elem.port" contains the values "port", "mask",
"compare" and "in_word". "port" holds the port number to read
from. The value read is masked with "mask", and compared with
"compare". A word access is used if "in_word" is nonzero.
Memory watches are similar to port watches, with "elem.mem"
containing "address", the memory address to watch, and "mask" and
"compare" as above.
Ctask Manual - Version 2.0 - 89-12-21 - Page 33
"tstate" contains the state of the element:
#define TSTAT_REMOVE 0xff
The element should be deleted.
#define TSTAT_IDLE 0
The element is not enqueued.
#define TSTAT_WATCH 1
The element is a watchpoint (port or memory).
It will be removed when the watch is activated.
#define TSTAT_CONTWATCH 2
The element is a watchpoint (port or memory).
It will not be removed on activation.
#define TSTAT_COUNTDOWN 3
The timeout counter is counted down. If the timeout is
reached, the element is removed from the queue.
#define TSTAT_REPEAT 4
The timeout counter is counted down. If the timeout is
reached, the timeout count is reloaded.
The "flags" field holds two bits:
#define TFLAG_BUSY 0x01
If this bit is set, the timer task is busy processing
this element. It may not be deleted or enqueued.
#define TFLAG_TEMP 0x80
If this bit is set, this is a temporary element, which
will be deleted if the timeout is reached, or the watch
condition is met.
The "user_parm" field may be used to pass pointers or other
values to timeout functions. It is not modified by the CTask
kernel routines.
Ctask Manual - Version 2.0 - 89-12-21 - Page 34
typedef struct tlink_rec far *tlinkptr;
struct telem_memwatch {
wordptr address;
word mask;
word compare;
};
struct telem_portwatch {
word port;
word mask;
word compare;
byte in_word;
};
struct telem_timeout {
dword reload;
};
struct tlink_rec {
queue link;
farptr strucp;
dword user_parm;
union {
struct telem_memwatch mem;
struct telem_portwatch port;
struct telem_timeout time;
} elem;
byte elkind;
byte tstate;
byte flags;
};
typedef struct tlink_rec tlink;
Ctask Manual - Version 2.0 - 89-12-21 - Page 35
The name link structure
If TSK_NAMED is enabled, all structures except the timer control
block contain a name link. All control blocks are linked and
named via this element.
"strucp" points to the head of the structure the name link is an
element of, with "kind" in the list field specifying the type of
structure:
#define TYP_TCB 1 task control block
#define TYP_FLAG 2 flag event
#define TYP_RESOURCE 3 resource event
#define TYP_COUNTER 4 counter event
#define TYP_MAILBOX 5 mailbox event
#define TYP_PIPE 6 byte pipe
#define TYP_WPIPE 7 word pipe
#define TYP_BUFFER 8 buffer
The head element of the name list (normally the group control
block) has its kind field set to zero.
The "name" field contains an up to 8-character name plus a zero
terminator.
#define NAMELENGTH 9
typedef struct name_rec far *nameptr;
struct name_rec {
queue_head list;
farptr strucp;
char name [NAMELENGTH];
};
typedef struct name_rec namerec;
The task control block structure
The "cqueue" field links the TCB into one of the queues. The
"qhead" pointer points to the head of the queue the task is
enqueued in, or will be enqueued in on the next schedule request
in the case of the current running task.
The "prior" field in the cqueue structure contains the tasks
current priority, with 0xffff the highest possible priority, and
0 the lowest. The "ini_prior" field initially contains the same
value. If variable priority is enabled, "prior" is incremented on
each scheduler call, and reset to "ini_prior" when the task is
activated.
Ctask Manual - Version 2.0 - 89-12-21 - Page 36
"stack" contains the saved task stack pointer (offset and
segment) if the task is not running. The field "stkbot" contains
the bottom address of the task stack. It is set by create_task,
but is currently not used anywhere else. Stack checking routines
might use this value to test for stack overflow and/or stack
usage. The "t_ax", "t_cx", ..., "t_ds" fields hold the register
contents while a task is inactive.
"state" and "flags" contain the tasks state and flags.
"timerq" is a tlink structure used to chain the tcb into the
timer queue if the task is waiting for a timeout, or into the
watch queue if the task is waiting for a watch event. See above
for a description of the tlink structure.
The fields "retptr" and "retsize" are used in event handling.
They are used when a task is waiting for an event by the task
activating the event, and also by timeout and wake to indicate
error returns. The use of these pointers eliminates the need to
loop for an event, which requires slightly more code in the event
handling routines, but reduces the need for task switching.
The "save_func" and "rest_func" pointers can optionally point to
task-switch save and restore functions. In special applications,
it may be necessary to save and restore global variables or other
items when a task is deactivated. The save function is called
when a task is deactivated, the restore function when a task is
activated. Both functions should be defined as
void far funcname (tcbptr tcb);
The functions are called with a pointer to the current TCB, DS
and ES are set up to point to the task's current data segment.
Please note that the data segment may be different from the
default data segment if the task switch occurred while the task
executed a DOS or BIOS function. Interrupts are disabled, and
should not be re-enabled. The stack is the scheduler's local
stack. Calling CTask-routines that might cause the scheduler to
be activated should be avoided.
The "user_ptr" field can hold any pointer or other value to be
used by the save/restore functions or to point to task-related
items. This field is not modified by the CTask routines.
The "group" and "homegroup" pointers link a task with it's
current task group, and with the base group that created the task
if the task spawned a secondary invocation of CTask.
The DOS-related fields "indos", "new", "base_psp", "psp_sssp",
and "swap_area" are used to save critical information on the
state of DOS. They should never be tampered with.
Ctask Manual - Version 2.0 - 89-12-21 - Page 37
The "name" namerec structure is present only if TSK_NAMED is
enabled. It may be used in debugging (see tsksnap.c for an
example), and in applications where the address of the structure
can not be passed directly.
typedef struct tcb_rec far *tcbptr;
struct tcb_rec {
tcbptr next;
tqueptr queue;
byteptr stack;
byteptr stkbot;
word prior;
word initprior;
byte state;
byte flags;
dlink timerq;
farptr retptr;
int retsize;
};
typedef struct tcb_rec tcb;
Task states
#define ST_KILLED 0
The task has been killed. Restarting the task is not
possible. Queue pointers are invalid.
#define ST_STOPPED 1
The task is not enqueued in any queue. To be in-cluded
in scheduling, it has to be explicitly started. The
queue head pointer is NULL.
#define ST_DELAYED 2
The task is enqueued in the timer queue only. When the
timer expires, it is placed in the eligible queue. The
queue head pointer is NULL.
#define ST_WAITING 3
The task is waiting for an event to happen. It can
also be chained into the timer queue if a timeout was
specified in the call. The queue head pointer points
to the queue head in the event control block for the
event the process is waiting on.
Ctask Manual - Version 2.0 - 89-12-21 - Page 38
#define ST_ELIGIBLE 4
The task is enqueued in the queue of processes eligible
for running. It can not be chained in the timer queue.
The queue head pointer points to the eligible queue.
#define ST_RUNNING 5
The task is the current running process. Although it is
not enqueued in any queue, the queue head pointer in
its control block is valid and points to the queue the
process will be enqueued in on the next schedule
request.
Possible state transitions and their reasons:
stopped -> eligible by start_task ()
delayed -> killed by kill_task ()
-> eligible by timer task, or wake_task ()
eligible -> killed by kill_task ()
-> running by scheduler
running -> killed by kill_task ()
-> stopped by delay (0)
-> delayed by delay (n != 0)
-> eligible by scheduler
-> waiting by wait_xxx ()
waiting -> killed by kill_task ()
-> eligible by event happening, timeout,
or wake_task()
Task flags
System flags:
#define F_TEMP 0x80
This tcb was allocated automatically, and must be
free'd on task kill.
#define F_STTEMP 0x40
The tasks stack was allocated automatically, and must
be free'd on task kill.
Ctask Manual - Version 2.0 - 89-12-21 - Page 39
User changeable flags:
#define F_CRIT 0x01
This task may not be preempted. It will run until it
clears this flag, delays itself, calls the scheduler
explicitly, or waits for an event.
The Group Control Block
Each invocation of CTask owns a group control block, gcb. This
structure holds all information relevant to this invocation only,
and chains all data structures created in this group.
The "home", "level", and "branch" fields interconnect the groups
(see the chapter on groups for more information).
The "creator" field identifies the creating task.
The "exit_addr", "create_psp", "save_psp", and "save_sssp" fields
hold the information necessary to allow spawning CTask
applications.
The "namelist" field contains the name of the group itself, and
links all structures created within this group.
struct group_rec {
gcbptr home;
gcbptr level;
gcbptr branch;
tcbptr creator;
dword exit_addr;
word create_psp;
word save_psp;
dword save_sssp;
namerec namelist;
};
The event control blocks
All event control blocks have two optional fields:
"name" is only present if TSK_NAMED is enabled. It is a namerec
structure as explained above.
Ctask Manual - Version 2.0 - 89-12-21 - Page 40
"flags" is only present if TSK_DYNAMIC is enabled. It contains
F_TEMP If this control block was allocated
automatically, and must be free'd on delete.
F_STTEMP If the buffer for this event was allocated auto-
matically, and must be free'd on delete (pipes
and buffers only).
The Ticker structure
The ticker is not an event structure in the true sense, since
there are no operations that wait on a ticker. It is the only
control structure linked by a single forward pointer. It contains
this "next" pointer, a doubleword "ticks" count that is counted
down towards zero on every hardware clock tick, and the optional
"flags" field. It is permissible to access and modify the "ticks"
field directly.
typedef struct ticker_rec far *tick_ptr;
typedef struct ticker_rec {
tick_ptr next;
dword ticks;
byte flags; (if TSK_DYNAMIC)
} ticker;
The Flag event structure
Contains two queues for processes waiting on a flag state (clear
or set), plus the flag state (0 = clear, 1 = set).
typedef struct {
queue_head wait_set;
queue_head wait_clear;
int state;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} flag;
typedef flag far *flagptr;
Ctask Manual - Version 2.0 - 89-12-21 - Page 41
The counter event structure
Similar to a flag, but contains a doubleword state counter.
typedef struct {
queue_head wait_set;
queue_head wait_clear;
dword state;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} counter;
typedef counter far *counterptr;
The resource event structure
Contains a queue for the tasks waiting for access to the
resource, a pointer to the current owner of the resource (to
check for illegal "release_resource" and nested request calls),
and the resource request counter (0 = free, <> 0 = in use).
typedef struct {
queue_head waiting;
queue_head owner;
word count;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} resource;
typedef resource far *resourceptr;
Ctask Manual - Version 2.0 - 89-12-21 - Page 42
The mailbox event structure
The msgptr type is only used internally to chain mail blocks into
the mailbox. The mailbox type contains a queue of the tasks
waiting for mail, and a first and last pointer for the chain of
mail blocks.
struct msg_header {
struct msg_header far *next;
};
typedef struct msg_header far *msgptr;
typedef mailbox far *mailboxptr;
typedef struct {
queue_head waiting;
msgptr mail_first;
msgptr mail_last;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} mailbox;
The pipe and word pipe event structure
Contains queues of the tasks waiting to read or write to the
pipe, indices for reading (outptr) and writing (inptr) into the
buffer, the buffer size, the number of bytes or words currently
in the pipe ("filled"), and the pointer to the buffer. A word
pipe is handled exactly the same as a byte pipe, the only
difference being the element size placed in the buffer. With
normal pipes, characters are buffered, with word pipes, words.
typedef struct {
queue_head wait_read;
queue_head wait_write;
queue_head wait_clear;
word bufsize;
word filled;
word inptr;
word outptr;
byteptr contents;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} pipe;
typedef pipe far *pipeptr;
Ctask Manual - Version 2.0 - 89-12-21 - Page 43
typedef struct {
queue_head wait_read;
queue_head wait_write;
queue_head wait_clear;
word bufsize;
word filled;
word inptr;
word outptr;
wordptr wcontents;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} wpipe;
typedef wpipe far *wpipeptr;
The buffer event structure
Contains resources for read and write access, the word pipe used
for buffering, and a message counter.
typedef struct {
resource buf_write;
resource buf_read;
wpipe pip;
word msgcnt;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} buffer;
typedef buffer far *bufferptr;
Ctask Manual - Version 2.0 - 89-12-21 - Page 44
CTask Routines
Installation and Removal
int install_tasker (int varpri, int speedup,
word flags, byteptr name);
Installs the multitasker. Must be called prior to any other
routine. The calling routine is defined as the main task,
and assigned the highest priority. To allow other tasks to
execute, the main task must have its priority reduced, be
delayed, or wait on an event.
Returns 1 if CTask was already present in the system, i.e.
this is a secondary invocation. Returns 0 if this is the
first installation of CTask.
"varpri" Enables variable priority if nonzero.
"speedup" is defined for the IBM PC/XT/AT as the clock tick
speedup factor. The timer tick frequency will be
set to
speedup ticks/sec msecs/tick
0 18.2 54.9 (normal clock)
1 36.4 27.5
2 72.8 13.7
3 145.6 6.9
4 291.3 3.4
Note that all timeouts are specified in tick
units, so changing the speedup parameter will
influence timeouts and delays. You can enable
CLOCK_MSEC in tskconf.h to allow timeouts to be
specified in milliseconds. The system clock will
not be disturbed by changing the speed. Using
values above 2 can lead to interrupt overruns on
slower machines and is not recommended.
"flags" controls various installation flags through the
bitwise OR of the following flags defined in
TSK.H:
IFL_VIDEO Install INT 10 access resource if set. No
protection of Video interrupt if clear.
Installation of the Video resource is safer,
but may slow down the system noticeably. It is
not necessary if you don't do screen-I/O
through the BIOS from your CTask program.
Ctask Manual - Version 2.0 - 89-12-21 - Page 45
IFL_DISK Install INT 13 access resource if set. No
protection of Disk interrupt if clear.
Installation of INT 13 is recommended for
better system safety. The resource will allow
parallel access to floppy and harddisk, but
might be incompatible with some special
programs (though none have yet been reported).
IFL_INT8_DIR Call original INT 8 directly if set.
This changes the timing for the timer inter-
rupt. If this flag is clear, the original
timer interrupt is chained to through a task,
so it can be preempted. If it is set, the
original interrupt is chained to directly, at
the start of timer interrupt processing. This
avoids compatibility problems with certain
TSR's and Networks, but may impair CTask
operations while those TSR's are active.
IFL_PRINTER Install INT 17 handler if set.
Installation of the printer interrupt handler
avoids lengthy busy waiting on printer output,
but might be incompatible with certain printer
redirectors.
IFL_INT15 Install IBM-AT INT 15 handler if set.
The IBM INT 15 handler allows avoiding some
busy waiting loops. It may, however, be
incompatible with some non-100% clones, and
with debuggers like Periscope IV. If you're
not sure, don't set this flag.
IFL_NODOSVARS
Do not copy the internal DOS variable area if
set. This flag may be used for incompatible
versions of DOS or it's clones, but background
DOS access will not work if this flag is set.
Use only for known incompatible versions, and
only if you do not intend to TSR or Spawn.
"name" is the name of the created task group.
void remove_tasker (void);
Uninstalls the multitasker. Must only be called from the
main task, and should be called before exiting the program.
Ctask Manual - Version 2.0 - 89-12-21 - Page 46
int ctask_resident (void);
This routine allows checking if another copy of CTask is
already resident. It should be called before install_tasker
is invoked.
Returns 1 if CTask is resident, 0 otherwise. It always
returns 1 after install_tasker has been called.
Searching for names
farptr find_name (byteptr name, int kind);
This routine searches for a task, group, or event structure
from the current task's group downward to the base group.
Returns a pointer to the structure, or a pointer to the name
record within the structure (depending on kind) if the name
was found, NULL otherwise.
This routine may be called before "install_tasker" has been
invoked to search names in resident copies.
"name" pointer to a zero-terminated, up to eight
character, name. Case is significant in names.
"kind" is the kind of structure to search for:
If -1, all structures are searched, and the first
structure with the given name is returned. In
this case, the returned pointer points to the
name record within the structure, to allow
identification of the structure. The "strucp"
pointer within that record points to the start of
the structure, the "kind" field in the link
structure identifies the type of the structure.
If kind is >= 0, the returned pointer is a
pointer to the start of the structure:
TYP_GROUP - Search for Groups only
TYP_FLAG - Search for Flags only
TYP_RESOURCE - Search for Resources only
TYP_COUNTER - Search for Counters only
TYP_MAILBOX - Search for Mailboxes only
TYP_PIPE - Search for Pipes only
TYP_WPIPE - Search for Word Pipes only
TYP_BUFFER - Search for Buffers only
Ctask Manual - Version 2.0 - 89-12-21 - Page 47
farptr find_group_name (gcbptr group, byteptr name, int kind);
Looks for a name in the specified group only.
Return value and parameters match those of find_group_name,
except
"group" pointer to a group control block.
Miscellaneous
void preempt_off (void);
Disables task preemption. This is the default after instal-
lation. With preemption turned off, only delays, event
waits, and explicit scheduler calls will cause a task
switch.
void preempt_on (void);
Enables task preemption. Task switches will occur on timer
ticks.
void schedule (void);
Explicit scheduling request. The highest priority eligible
task will be made running.
void c_schedule (void);
Conditional scheduling request. Scheduling will take place
only if preemption is allowed.
void yield (void);
Explicit scheduling request, with task priority temporarily
set to zero. If a task has to wait for some external event
in a polling loop, this call may be used to allow tasks of
lower priority to execute.
void tsk_dis_preempt (void)
Temporarily disable task preemption. Preemption will be re-
enabled by tsk_ena_preempt or an unconditional scheduler
call.
Ctask Manual - Version 2.0 - 89-12-21 - Page 48
void tsk_ena_preempt (void)
Re-enable task preemption. Note that tsk_dis_preempt and
tsk_ena_preempt do not change the global preemption state
set by preempt_off and preempt_on.
int tsk_dis_int (void)
Disable interrupts. Returns the state of the interrupt flag
prior to this call (1 if interrupts were enabled).
void tsk_ena_int (int state)
Enables interrupts if "state" is nonzero. Normally used in
conjunction with tsk_dis_int.
The routines tsk_dis_int and tsk_ena_int may be used in a simpli-
fied scheme with the defines
CRITICAL; Declares "int crit_intsav;".
C_ENTER; Expands to "crit_intsav = tsk_dis_int ();"
C_LEAVE; Expands to "tsk_ena_int (crit_intsav);".
void tsk_cli (void)
Disables interrupts (intrinsic function).
void tsk_sti (void)
Unconditionally enables interrupts (intrinsic function).
void tsk_outp (int port, byte b)
Outputs the value "b" to hardware-port "port" (intrinsic
function).
byte tsk_inp (int port)
Returns the value read from port "port" (intrinsic
function).
Ctask Manual - Version 2.0 - 89-12-21 - Page 49
The following entry points may be used from assembler routines:
extrn _tsk_scheduler: far
Direct entry into the scheduler. The stack must be set up as
for an interrupt handler, e.g.
pushf
cli
call _tsk_scheduler
extrn _sched_int: far
Conditional scheduling call. The stack must be set up as for
an interrupt handler.
Task Operations
tcbptr create_task (tcbptr task, funcptr func, byteptr stack,
word stksz, word prior, farptr arg
[, byteptr name]);
Initialises a task. Must be called prior to any other opera-
tions on this task. The task is in the stopped state after
creation. It must be started to be able to run.
Returns a pointer to the created tcb, or NULL on error.
"task" is a pointer to a tcb (NULL for automatic allo-
cation).
"func" is a pointer to a void far function, with a
single dword sized parameter.
"stack" is a pointer to a stack area for the task (NULL
for automatic allocation).
"stksz" is the size of the stack area in bytes.
"prior" is the tasks priority (0 lowest, 0xffff highest).
"arg" is an argument to the created task. It may be
used to differentiate between tasks when using
the same function in different tasks.
"name" is the name of the task, up to eight characters,
plus zero-terminator. This parameter is defined
only if TSK_NAMEPAR is enabled in tskconf.h.
Ctask Manual - Version 2.0 - 89-12-21 - Page 50
void kill_task (tcbptr task);
Kills a task. The task can no longer be used.
int start_task (tcbptr task);
Starts a task, i.e. makes it eligible for running.
Returns 0 if task was started, -1 if the task was not
stopped. A value of NULL for "task" will start the "main
task".
int stop_task (tcbptr task);
Stops a task, i.e. removes it from all queues. A value of
NULL for "task" will stop the "main task".
Returns 0 on success.
int wake_task (tcbptr task);
Prematurely wakes a delayed or waiting task. If the task was
waiting for an event, it will be removed from the waiting
queue, with the operation terminating with an error return
value.
Returns 0 if task was waked, -1 if the task was not delayed
or waiting. A value of NULL for "task" will wake the "main
task".
void set_priority (tcbptr task, word prior)
Sets the priority of the specified task to "prior". Note
that you should NOT modify the priority field in the tcb
structure directly. A value of NULL for "task" will set the
priority of the "main task".
word get_priority (tcbptr task)
Returns the current priority of the specified task. A value
of NULL for "task" will get the priority of the "main task".
Ctask Manual - Version 2.0 - 89-12-21 - Page 51
void set_task_flags (tcbptr task, byte flags)
Sets the flags of the task to "flags". Currently, the only
flag that can be changed is
F_CRIT the task can not be preempted if set.
Note that you may NOT modify the flag field in the tcb
structure directly. A value of NULL for "task" will set the
flags of the "main task".
void set_funcs (tcbptr task, funcptr save, funcptr rest);
Sets the save and restore functions for the specified task.
The save function is called whenever the task is deacti-
vated, the restore function is called whenever the task is
scheduled. This allows saving of critical system states upon
a task switch. Both functions are called with a far call,
the pointer to the task control block is passed as
parameter. A value of NULL for "task" will set the functions
of the "main task". To deactivate one or both functions,
pass NULL as function parameter.
farptr set_user_ptr (tcbptr task, farptr uptr);
Sets the user pointer in the specified TCB, and returns it's
previous value. The user pointer may be used to point to
memory blocks local to a task, or to hold other task-
specific values. It is never accessed or modified by the
CTask kernel. A value of NULL for "task" will set the user
pointer of the "main task".
farptr get_user_ptr (tcbptr task);
Returns the user pointer of the specified TCB. A value of
NULL for "task" will get the user pointer of the "main
task".
Timer Operations
Timeouts are normally specified in timer ticks. If CLOCK_MSEC is
enabled, timeouts will be converted to the nearest number of
timer ticks automatically. The global word variable
"ticks_per_sec" contains a rough estimate of the number of ticks
per second even if CLOCK_MSEC is not enabled.
Ctask Manual - Version 2.0 - 89-12-21 - Page 52
Event wait Timeouts
When waiting for an event, a timeout may be specified. The
operation will terminate with an error return value if the
timeout is reached before the event occurs. NOTE that the timeout
parameter is not optional. To specify no timeout, use the value 0.
"Tickers"
It sometimes is desirable to measure timing intervals or global
timeouts without creating special tasks, or while waiting for
external events in polling loops. A low overhead way of doing
this is the "ticker" structure. When this structure is linked
into the ticker chain, the doubleword "ticks" field is decre-
mented on every timer tick (in the timer interrupt itself), until
it reaches zero.
tick_ptr create_ticker (tick_ptr elem, dword val);
Links a ticker element into the ticker chain. The ticks
field is initialized to "val", and will be decremented to
zero. If the elem pointer is NULL, a new ticker element will
be created.
Returns the ticker element pointer, NULL on error.
void delete_ticker (tick_ptr elem);
Unlinks a ticker element from the ticker chain. Countdown
will stop. If the element was allocated dynamically, it will
be deleted.
void set_ticker (tick_ptr elem, dword val);
Sets the ticks field to the given value. If the ticks field
is modified directly instead of through this routine,
interrupts should be disabled when writing a doubleword
value.
dword get_ticker (tick_ptr elem)
Returns the current ticker value. If the ticks field is read
directly instead of through this routine, interrupts should
be disabled when reading the doubleword value.
Ctask Manual - Version 2.0 - 89-12-21 - Page 53
Delays
int t_delay (dword ticks);
Delay the current task for "ticks" clock ticks/milliseconds.
If ticks is 0, the task is stopped.
Returns TIMEOUT if the delay expired, WAKE if the task was
activated by wake_task.
Timed Events and Watch Events
You can create timer control blocks and memory/port watch blocks
to
- set a flag
- increment a counter
- wake up a task
- call a function
after a specified timeout interval, or when a certain condition
is met. The operation may optionally be repeated.
Watch control blocks are checked on every timer tick. They allow
memory locations and I/O ports to be checked for a certain value,
or for a change in value. When the specified condition is met,
the given action is executed.
For calling functions on a timeout or watch, the following must
be noted:
- Timeout/watch functions should be as short as possible,
since they run at the highest priority.
- The stack and data area is the area of the timer task. Be
careful when referencing variables.
- Timeout/watch functions may not use any functions that could
cause the timer task to be made waiting.
Since version 2.0, timeout/watch functions are called with
interrupts enabled. The definition of the timeout function is
void far funcname (dword user_parm);
The function is called with the "userpar" parameter specified
with the create_timer, create_memory_watch, and create_port_watch
calls.
Ctask Manual - Version 2.0 - 89-12-21 - Page 54
tlinkptr create_timer (tlinkptr elem, dword tout, farptr strucp,
byte kind, byte rept [,dword userpar]);
Create a timer control block.
Returns the address of the control block, NULL on error.
"elem" is the control block to initialise (NULL for
auto-matic allocation).
"tout" specifies the timeout interval.
"strucp" points to the structure to be used. This is a
flagptr for setting a flag, a counterptr for
increasing counters, a tcbptr for waking a task,
or a funcptr for calling a function.
"kind" gives the kind of structure "strucp" points to.
It must be one of
TKIND_WAKE for task-wakeup
TKIND_PROC for function call
TKIND_FLAG for flag set
TKIND_COUNTER for counter increment.
"rept" if nonzero, the action will be repeated on every
"tout" timeout interval.
"userpar" is a doubleword value that is passed to the
timeout function if kind is TKIND_PROC. This
parameter is optional.
NOTE: Timer control blocks can not be named.
void change_timer (tlinkptr elem, dword tout, byte rept);
Changes the timeout value and/or the repeat flag for an
existing timer control block. If "tout" is zero, the call
has the same effect as delete_timer.
void delete_timer (tlinkptr elem);
Deletes a timer control block, and removes it from the
timeout queue.
Ctask Manual - Version 2.0 - 89-12-21 - Page 55
tlinkptr create_memory_watch (tlinkptr elem, farptr address,
word mask, word compare,
byte cmpkind, farptr strucp,
byte kind, byte rept
[, dword userpar]);
Creates a memory watch control block. Memory watches compare
a byte or word value at the specified address against a
comparison value. The value loaded is always a word, the
"mask" parameter may be used to restrict comparison to a
byte.
Returns the watch control block pointer, or NULL on error.
"elem" is the control block to initialise (NULL for
automatic allocation).
"address" is a pointer to the memory location to watch.
"mask" is a bitmask to mask out irrelevant bits before
comparison.
"compare" is the value the memory contents are to be
compared with.
"cmpkind" is the kind of comparison to be used:
TCMP_EQ (*address & mask) == compare
TCMP_NE (*address & mask) != compare
TCMP_GE (*address & mask) >= compare (unsigned)
TCMP_LE (*address & mask) <= compare (unsigned)
TCMP_GES (*address & mask) >= compare (signed)
TCMP_LES (*address & mask) <= compare (signed)
TCMP_CHG Change in value:
(*address & mask) != compare
compare = *address & mask
"strucp" points to the structure to be used. This is a
flagptr for setting a flag, a counterptr for
increasing counters, a tcbptr for waking a task,
or a funcptr for calling a function.
"kind" gives the kind of structure "strucp" points to.
It must be one of
TKIND_WAKE for task-wakeup
TKIND_PROC for function call
TKIND_FLAG for flag set
TKIND_COUNTER for counter increment.
"rept" if nonzero, the action will be repeated whenever
the condition is met.
Ctask Manual - Version 2.0 - 89-12-21 - Page 56
"userpar" is a doubleword value that is passed to the watch
function if kind is TKIND_PROC. This parameter is
optional.
NOTE: Watch control blocks can not be named.
int wait_memory (farptr address,
word mask, word compare, byte cmpkind)
Delays the current task until the specified condition is
met. Parameters are the same as in create_memory_watch. No
timeout can be specified.
Returns WATCH if the watch condition was met, WAKE if the
task was activated by wake_task.
tlinkptr create_port_watch (tlinkptr elem,
word port, byte in_word,
word mask, word compare,
byte cmpkind, farptr strucp,
byte kind, byte rept
[, dword userpar]);
Creates a port watch control block. Port watches compare a
byte or word value read from the specified port address
against a comarison value. The value read is either a word
or a byte, depending on the "in_word" parameter.
Returns the watch control block pointer, or NULL on error.
"elem" is the control block to initialise (NULL for
automatic allocation).
"port" is the port address to read.
"in_word" Specifies whether a word or byte input
instruction is to be used. If zero, a byte is
input, if nonzero, a word is read from the port.
"mask" is a bitmask to mask out irrelevant bits before
comparison.
"compare" is the value the port contents are to be compared
with.
Ctask Manual - Version 2.0 - 89-12-21 - Page 57
"cmpkind" is the kind of comparison to be used:
TCMP_EQ (in(port) & mask) == compare
TCMP_NE (in(port) & mask) != compare
TCMP_GE (in(port) & mask) >= compare (unsigned)
TCMP_LE (in(port) & mask) <= compare (unsigned)
TCMP_GES (in(port) & mask) >= compare (signed)
TCMP_LES (in(port) & mask) <= compare (signed)
TCMP_CHG Change in value:
(in(port) & mask) != compare
compare = in(port) & mask
(NOTE: The port is read only once.)
"strucp" points to the structure to be used. This is a
flagptr for setting a flag, a counterptr for
increasing counters, a tcbptr for waking a task,
or a funcptr for calling a function.
"kind" gives the kind of structure "strucp" points to.
It must be one of
TKIND_WAKE for task-wakeup
TKIND_PROC for function call
TKIND_FLAG for flag set
TKIND_COUNTER for counter increment.
"rept" if nonzero, the action will be repeated whenever
the condition is met.
"userpar" is a doubleword value that is passed to the watch
function if kind is TKIND_PROC. This parameter is
optional.
NOTE: Watch control blocks can not be named.
int wait_port (word port, byte in_word,
word mask, word compare, byte cmpkind)
Delays the current task until the specified condition is
met. Parameters are the same as in create_port_watch. No
timeout can be specified.
Returns WATCH if the watch condition was met, WAKE if the
task was activated by wake_task.
void delete_watch (tlinkptr elem);
Ctask Manual - Version 2.0 - 89-12-21 - Page 58
The specified watch element is removed from the watch queue.
NOTE: delete_timer, delete_watch, and change_timer should not be
used on automatically allocated timer/watch control blocks. Since
such blocks are deallocated once the timeout expires or the
condition is met (except for repeat operations), the validity of
the pointer is not guaranteed.
Event Operations
Resources
A Resource is either in use or free, the default state is free.
If a task has requested a resource, its state is in use, and the
requesting task is said to "own" the resource. Tasks requesting
a resource while it is in use will be made waiting. If the
resource is released, the highest priority waiting task is made
eligible, and is assigned the resource. Interrupt handlers may
not use resource functions other than "check_resource".
To facilitate use of resources in nested routines, CTask allows a
resource to be requested by a Task already owning it. Different
calls may be used to request resources, depending on how such
nested calls should be handled. The standard request_resource and
c_request_resource calls just mark the resource as in use by
setting the owner count to 1, and the first nested routine that
releases the resource will cause the resource to be freed. The
request_cresource and c_request_cresource calls increment the
owner count, so the resource will only be freed upon a matching
count of requests and releases.
resourceptr create_resource (resourceptr rsc [, byteptr name]);
This initialises a resource. Must be used prior to any other
operations on a resource.
Returns the address of the control block, NULL on error.
"rsc" is the resource control block (NULL for automatic
allocation).
"name" is the name of the resource, up to eight
characters, plus zero-terminator. This parameter
is defined only if TSK_NAMEPAR is enabled in
tskconf.h.
Ctask Manual - Version 2.0 - 89-12-21 - Page 59
void delete_resource (resourceptr rsc);
Calling this routine is optional. It will kill all tasks
waiting for the resource.
void release_resource (resourceptr rsc);
Decrement the owner count, and release the resource if it is
zero. If the task does not own the resource, the call is
ignored. If the resource is released, and tasks are waiting
for access, the highest priority task will gain access to
the resource.
int request_resource (resourceptr rsc, dword timeout);
Requests the resource. If it is not available, the task is
suspended. A timeout may be specified.
Returns 0 if resource was allocated, TIMEOUT if a timeout
occurred, WAKE on wake.
This call is ignored (returns a 0) if the calling task
already owns the resource.
int request_cresource (resourceptr rsc, dword timeout);
Requests the resource. If it is not available, the task is
suspended. A timeout may be specified.
Returns 0 if resource was allocated, TIMEOUT if a timeout
occurred, WAKE on wake.
This call increments the owner count, and returns 0, if the
calling task already owns the resource.
int c_request_resource (resourceptr rsc);
Requests the resource only if it is available.
Returns 0 if resource was allocated, -1 if unavailable.
int check_resource (resourceptr rsc);
Returns 0 if resource is allocated, 1 if free.
Ctask Manual - Version 2.0 - 89-12-21 - Page 60
Flags
A Flag can be either on or off, the default state is off (0).
Tasks can wait on either state of the flag. If the state is
changed, all tasks waiting for the state are made eligible.
Interrupt handlers may use the "set_flag", "clear_flag", and
"check_flag" functions.
flagptr create_flag (flagptr flg [, byteptr name]);
This initialises a flag. Must be used prior to any other
operations on a flag. The state is set to 0.
Returns the address of the control block, NULL on error.
"flg" is the flag control block (NULL for automatic
allocation).
"name" is the name of the flag, up to eight characters,
plus zero-terminator. This parameter is defined
only if TSK_NAMEPAR is enabled in tskconf.h.
void delete_flag (flagptr flg);
Calling this routine is optional. It will kill all tasks
waiting for the flag.
void set_flag (flagptr flg);
This sets the flag. All tasks waiting for the set state will
be made eligible for running.
void clear_flag (flagptr flg);
This clears the flag. All tasks waiting for the clear state
will be made eligible for running.
int wait_flag_set (flagptr flg, dword timeout);
Waits for the set state of the flag. If the flag is not set,
the task is suspended. A timeout may be specified.
Returns 0 if the flag was set, TIMEOUT on timeout, WAKE on
wake.
Ctask Manual - Version 2.0 - 89-12-21 - Page 61
int wait_flag_clear (flagptr flg, dword timeout);
Waits for the clear state of the flag. If the flag is not
clear, the task is suspended. A timeout may be specified.
Returns 0 if the flag was cleared, TIMEOUT on timeout, WAKE
on wake.
int clear_flag_wait_set (flagptr flg, dword timeout)
Combines the operations clear_flag and wait_flag_set.
Returns 0 if the flag was set, TIMEOUT on timeout, WAKE on
wake.
int check_flag (flagptr flg);
Returns 0 if flag clear, 1 if set.
Counters
A Counter can have any value from 0L to 0xffffffffL, the default
value is 0. Tasks can wait for a counter being zero or non-zero.
If the counter is cleared or decremented to zero, all tasks wai-
ting for the zero condition are made eligible. If the counter is
incremented, the highest priority task waiting for non-zero is
made eligible, and the counter is decremented by one. Interrupt
handlers may use the "clear_counter", "inc_counter",
"set_counter", and "check_counter" functions.
counterptr create_counter (counterptr cnt [, byteptr name]);
This initialises a counter. Must be used prior to any other
operations on a flag. The value is set to 0.
Returns the address of the control block, NULL on error.
"cnt" is the counter control block (NULL for automatic
allocation).
"name" is the name of the counter, up to eight
characters, plus zero-terminator. This parameter
is defined only if TSK_NAMEPAR is enabled in
tskconf.h.
Ctask Manual - Version 2.0 - 89-12-21 - Page 62
void delete_counter (counterptr cnt);
Calling this routine is optional. It will kill all tasks
waiting for the counter.
void clear_counter (counterptr cnt);
Clears the counter to zero. All tasks waiting for the zero
state will be made eligible for running.
int wait_counter_set (counterptr cnt, dword timeout);
Waits for the counter having a nonzero value. If the value
is zero, the task is suspended. The value is decremented
when the task gets access to the counter. A timeout may be
specified.
Returns 0 if the counter was nonzero, TIMEOUT on timeout,
WAKE on wake.
int wait_counter_clear (counterptr cnt, dword timeout);
Waits for the counter having a zero value. If the value is
nonzero, the task is suspended. A timeout may be specified.
Returns 0 if the counter was zero, TIMEOUT on timeout, WAKE
on wake.
void inc_counter (counterptr cnt);
Increments the counter. If tasks are waiting for the nonzero
state, the highest priority task is given access to the
counter.
void set_counter (counterptr cnt, dword val);
Sets the counter to the given value. If "val" is nonzero,
and tasks are waiting for the set state, the highest
priority tasks are made eligible, and "val" is decremented,
until either "val" reaches zero, or there are no more tasks
waiting. If val is, or is counted down to, zero, all tasks
waiting for the zero state are made eligible.
dword check_counter (counterptr cnt);
Returns the current value of the counter.
Ctask Manual - Version 2.0 - 89-12-21 - Page 63
Mailboxes
A Mailbox can hold any number of mail blocks. Tasks can send mail
to a mailbox and wait for mail to arrive. A mail block is
assigned to the highest priority waiting task. Care must be
exercised not to re-use a mail block until it has been processed
by the receiving task. The mail block format is user defineable,
with the first doubleword in a block reserved for the tasking
system. Interrupt handlers may use the "send_mail",
"c_wait_mail", and "check_mailbox" functions.
Note that mailboxes are well suited to provide the mechanism for
managing a chain of (equally sized) free mail blocks. On
initialization, all free blocks would be "sent" to the manager
box. Requesting a free block would use "wait_mail", and freeing a
used block would again "send" it to the manager mailbox.
mailboxptr create_mailbox (mailboxptr box [, byteptr name]);
This initialises a mailbox. Must be used prior to any other
operations on a mailbox.
Returns the address of the control block, NULL on error.
"box" is the mailbox control block (NULL for automatic
allocation).
"name" is the name of the mailbox, up to eight
characters, plus zero-terminator. This parameter
is defined only if TSK_NAMEPAR is enabled in
tskconf.h.
void delete_mailbox (mailboxptr box);
Calling this routine is optional. It will kill all tasks
waiting for mail.
void send_mail (mailboxptr box, farptr msg);
Sends a message to the specified mailbox. If tasks are
waiting for mail, the highest priority task will get it.
farptr wait_mail (mailboxptr box, dword timeout);
Waits for mail. If no mail is available, the task is
suspended. A timeout may be specified.
Returns the pointer to the received mail block, TTIMEOUT on
timeout, TWAKE on wake.
Ctask Manual - Version 2.0 - 89-12-21 - Page 64
farptr c_wait_mail (mailboxptr box);
Reads mail only if mail is available.
Returns NULL if there is no mail, else a pointer to the
received message.
int check_mailbox (mailboxptr box);
Returns 0 if mailbox is empty, 1 otherwise.
Pipes
A Pipe has a buffer to hold character or word sized items. An
item may be written to a pipe if there is space in the buffer.
Otherwise, the writing task will be made waiting. A reading task
will be made waiting if the buffer is empty. If an item has been
read, the highest priority task waiting to write to the pipe will
be allowed to write. Interrupt handlers may only use pipe
functions "check_pipe", "c_write_pipe", and "c_read_pipe".
Note that the values -1 and -2 (0xffff and 0xfffe) should be
avoided when writing to word pipes. These values are used to mark
timeout and wake when reading, or pipe empty when checking a word
pipe, and thus may lead to erroneous operation of your routines.
pipeptr create_pipe (pipeptr pip, farptr buf, word bufsize
[, byteptr name]);
wpipeptr create_wpipe (wpipeptr pip, farptr buf, word bufsize
[, byteptr name]);
This initialises a pipe. Must be used prior to any other
operations on a pipe. "bufsize" specifies the buffer size in
bytes. With word pipes, the buffer size should be divisible
by two.
Returns the address of the control block, NULL on error.
"pip" is the pipe/wpipe control block (NULL for
automatic allocation).
"name" is the name of the pipe, up to eight characters,
plus zero-terminator. This parameter is defined
only if TSK_NAMEPAR is enabled in tskconf.h.
Ctask Manual - Version 2.0 - 89-12-21 - Page 65
void delete_pipe (pipeptr pip);
void delete_wpipe (wpipeptr pip);
Calling this routine is optional. It will kill all tasks
waiting to read or write messages.
int read_pipe (pipeptr pip, dword timeout);
word read_wpipe (wpipeptr pip, dword timeout);
Read an item from a pipe. If no item is available, the task
is suspended. A timeout may be specified.
Returns the item, or TIMEOUT on timeout, WAKE on wake.
int c_read_pipe (pipeptr pip);
word c_read_wpipe (wpipeptr pip);
Reads an item from a pipe only if one is available.
Returns the received item, or -1 if none is available.
int write_pipe (pipeptr pip, byte ch, dword timeout);
int write_wpipe (wpipeptr pip, word ch, dword timeout);
Writes an item to a pipe. If the buffer is full, the task is
suspended. A timeout may be specified. If tasks are waiting
to read, the item will be assigned to the highest priority
waiting task.
Returns 0 on success, or TIMEOUT on timeout, WAKE on wake.
int c_write_pipe (pipeptr pip, byte ch);
int c_write_wpipe (wpipeptr pip, word ch);
Writes an item to a pipe only if enough space is available.
Returns 0 on success, or -1 if no space available.
int wait_pipe_empty (pipeptr pip, dword timeout);
int wait_wpipe_empty (wpipeptr pip, dword timeout);
Waits for the pipe to be emptied. If the pipe is already
empty on entry, the task continues to run.
Returns 0 on empty, TIMEOUT on timeout, WAKE on wake.
Ctask Manual - Version 2.0 - 89-12-21 - Page 66
int check_pipe (pipeptr pip);
word check_wpipe (pipeptr pip);
Returns -1 if the pipe is empty, else the first item in the
pipe. The item is not removed from the pipe.
word pipe_free (pipeptr pip);
word wpipe_free (wpipeptr pip);
Returns the number of free items available in the pipe.
void flush_pipe (pipeptr pip)
void flush_wpipe (wpipeptr pip)
Clears the pipe. Tasks waiting for the empty state are made
eligible.
Buffers
A Buffer has a buffer to hold message strings. A message may be
written to a buffer if it fits into the buffer. Otherwise, the
writing task will be made waiting. A reading task will be made
waiting if the buffer is empty. If a message has been read, the
highest priority task waiting to write to the buffer will be
allowed to write if its message fits into the available space.
Interrupt handlers may not use buffer functions other than
"check_buffer".
The buffer routines are implemented using resources and pipes,
and thus are not part of the true "kernel" routines.
bufferptr create_buffer (bufferptr buf, farptr pbuf, word bufsize
[, byteptr name]);
This initialises a buffer. Must be used prior to any other
operations on a buffer.
The minimum buffer size is the length of the longest
expected message plus two.
Returns the address of the control block, NULL on error.
"buf" is the buffer control block (NULL for automatic
allocation).
"name" is the name of the buffer, up to eight
characters, plus zero-terminator. This parameter
is defined only if TSK_NAMEPAR is enabled in
tskconf.h.
Ctask Manual - Version 2.0 - 89-12-21 - Page 67
void delete_buffer (bufferptr buf);
Calling this routine is optional. It will kill all tasks
waiting to read or write messages.
int read_buffer (bufferptr buf, farptr msg, int size,
dword timeout);
Read a message from a buffer. If no message is available,
the task is suspended. A timeout may be specified.
The message will be copied to the buffer "buf", with a
maximum length of "size" bytes.
Returns the length of the received message, TIMEOUT on
timeout, WAKE on wake.
int c_read_buffer (bufferptr buf, farptr msg, int size);
Reads a message from a buffer only if one is available.
Returns the length of the received message, or -1 if none is
available.
int write_buffer (bufferptr buf, farptr msg, int size,
dword timeout);
Writes a message from "msg" with length "size" to a buffer.
If not enough space for the message is available, the task
is suspended. A timeout may be specified. If tasks are
waiting for messages, the highest priority task will get it.
Returns the length of the message, TIMEOUT on timeout, WAKE
on wake, -3 on error (length < 0 or length > buffer size).
int c_write_buffer (bufferptr buf, farptr msg, int size);
Writes a message to a buffer only if enough space is
available.
Returns the length of the message, -1 if no space available,
or -3 on error (length < 0 or length > buffer size).
word check_buffer (bufferptr buf);
Returns the current number of bytes (not messages) in the
buffer, including the length words.
Ctask Manual - Version 2.0 - 89-12-21 - Page 68
The Keyboard Handler
word t_read_key (void)
Waits for a key to be entered. Returns the ASCII-code in the
lower byte, and the scan-code in the upper byte, or WAKE
(0xfffe) on wake.
word t_wait_key (dword timeout)
Waits for a key to be entered. Returns the ASCII-code in the
lower byte, and the scan-code in the upper byte, or TIMEOUT
(0xffff) on timeout, WAKE (0xfffe) on wake.
word t_keyhit (void)
Returns -1 (0xffff) if no key was entered, else the value of
the key. The key is not removed.
The Serial I/O handler
The serial I/O handler provides full duplex interrupt driven I/O
on the serial ports. Support for COM1 and COM2 is included,
adding other ports is possible by changing the source and/or
defining ports on-line.
int v24_define_port (int base, byte irq, byte vector)
Defines a new COM-port. This routine can only be used if
TSK_DYNAMIC is enabled.
"base" is the port base I/O address.
"irq" is the IRQ-line number (0-7 for XT, 0-15 for AT)
the port uses for interrupts.
"vector" is the interrupt vector number (0-0xff).
The return value is the internal port number for use with
v24_install. -1 is returned on error (memory full).
Ctask Manual - Version 2.0 - 89-12-21 - Page 69
sioptr v24_install (int port, int init,
farptr rcvbuf, word rcvsize,
farptr xmitbuf, word xmitsize);
Installs the handler for the specified port. Currently,
ports 0 (COM1) and 1 (COM2) are supported by default. Both
ports may be used simultaneously, the buffer areas for the
ports must not be shared.
"rcvbuf" is a word pipe buffer area for received
characters. May be NULL for automatic allocation.
"rcvsize" specifies the receive buffer size in bytes. Note
that the buffer will hold rcvsize / 2 received
characters.
"xmitbuf" is a byte pipe buffer for characters waiting for
transmission. May be NULL for automatic
allocation.
"xmitsize" gives the transmit buffer size in bytes.
"port" Port number to install (0: COM1, 1: COM2).
If the port number is ORed with 0x80, the port is
*relative*. This means that the entry in the BIOS
table for COM-Ports is used to search the tables
internal to the driver for the port information,
instead of using the table entry directly. If the
port address cannot be found, the driver returns
with an error code. Note that ports are numbered
from 0, so to specify relative COM1, pass 0x80 as
parameter.
"init" if non-zero, the port is initialised with the
default values specified in the source. If the
parameter is zero, the control registers and baud
rates on entry are not modified.
The return value is a pointer to the sio control block for
use with the other driver routines. NULL is returned on
error (invalid port number, bad buffer sizes, nonexistent
hardware, out of memory).
Ctask Manual - Version 2.0 - 89-12-21 - Page 70
void v24_remove (sioptr sio, int restore);
Removes the driver for the specified control block. Should
be called for all ports installed before exiting the
program.
If the "restore" parameter is nonzero, the control registers
and the baud rate that were set before installation of the
driver are restored. If the parameter is zero, the values
are not changed.
void v24_remove_all (void)
Removes all installed serial i/o drivers. This routine is
automatically called on remove_tasker if drivers were
installed. Note that you can not specify a restore
parameter, the default restore parameter is used (constant
in tsksio.c).
void v24_change_rts (sioptr sio, int on);
Changes the state of the RTS output line. A nonzero value
for "on" will turn the output on.
void v24_change_dtr (sioptr sio, int on);
Changes the state of the DTR output line. A nonzero value
for "on" will turn the output on.
void v24_change_baud (sioptr sio, long rate);
Changes the baud rate. The following baud rates are suppor-
ted:
50, 75, 110, 134, 150, 300, 600,
1200, 1800, 2000, 2400, 3600, 4800, 7200, 9600,
19200, 38400.
Note that baud rates above 9600 may cause problems on slow
machines.
Ctask Manual - Version 2.0 - 89-12-21 - Page 71
void v24_change_parity (sioptr sio, int par);
Changes parity. The parameter must be one of the following
values defined in "sio.h":
PAR_NONE no parity checks
PAR_EVEN even parity
PAR_ODD odd parity
PAR_MARK mark parity
PAR_SPACE space parity
void v24_change_wordlength (sioptr sio, int len);
Changes word length. Values 5, 6, 7, 8 may be given.
void v24_change_stopbits (sioptr sio, int n);
Changes Stopbits. Values 1 and 2 are allowed.
void v24_watch_modem (sioptr sio, byte flags);
Watches the modem status lines to control transmission.
"flags" Specifies the input lines that must be active to
allow transmission. Transmission will stop if one
of the lines goes inactive. The parameter may be
combined from the following values defined in
"sio.h":
CTS to watch clear to send
DSR to watch data set ready
RI to watch ring indicator
CD to watch carrier detect
A value of zero (the default) will allow transmission
regardless of modem status.
Ctask Manual - Version 2.0 - 89-12-21 - Page 72
void v24_protocol (sioptr sio, int prot,
word offthresh, word onthresh);
Sets the handshake protocol to use.
"prot" specifies the protocol. This parameter may be
combined from the following values:
XONXOFF to enable XON/XOFF (DC1/DC3) handshake
RTSCTS to enable RTS/CTS handshake
"offthresh" specifies the minimum number of free items in
the receive buffer. If this threshold is reached,
an XOFF is transmitted and/or the RTS line is
inactivated.
"onthresh" specifies the minimum number of items that must
be free before XON is transmitted and/or the RTS
line is re-activated.
Enabling XONXOFF will remove all XON and XOFF characters
from the input stream. Transmission will be disabled when
XOFF is received, and re-enabled when XON is received.
Enabling RTSCTS will stop transmission when the CTS modem
input line is inactive.
int v24_send (sioptr sio, byte ch, dword timeout);
Transmits the character "ch". A timeout may be specified.
Returns TIMEOUT on timeout, WAKE on wake, else 0.
int v24_receive (sioptr sio, dword timeout);
Waits for a received character. A timeout may be specified.
Returns TIMEOUT on timeout, WAKE on wake, else the character
in the lower byte, plus an error code in the upper byte. The
error code is the combination of
0x02 overrun error
0x04 parity error
0x08 framing error
0x10 break interrupt.
int v24_check (sioptr sio);
Returns -1 if no receive character is available, else the
next character from the pipe. The character is not removed.
Ctask Manual - Version 2.0 - 89-12-21 - Page 73
int v24_overrun (sioptr sio);
Checks for receive pipe overrun. Returns 1 if an overrun
occurred, 0 otherwise. Clears the overrun flag.
int v24_modem_status (sioptr sio);
Returns the current modem status word. The CTS, DSR, RI, and
CD defines may be used to extract the modem input line
status.
int v24_complete (sioptr sio);
Returns 1 if all characters in the transmit pipe have been
sent, else 0.
int v24_wait_complete (sioptr sio, dword timeout);
Waits for the transmit pipe to be empty. Returns TIMEOUT on
timeout, WAKE on wake, else 0.
void v24_flush_receive (sioptr sio);
Flushes the receive pipe.
void v24_flush_transmit (sioptr sio)
Flushes the transmit pipe. Tasks waiting for transmit
completion are made eligible.
Ctask Manual - Version 2.0 - 89-12-21 - Page 74
The Printer Output Driver
The printer output driver provides for buffered output to up to
three printer ports (more can be added by editing the source).
Interrupt or polling may be selected. Due to the usual hardware
implementation of printers and the printer interface, using
polling is recommended. When using interrupts, you should not
simultaneously install both port 0 and port 1 with interrupt
enabled, since both ports share the same interrupt line.
int prt_install (int port, byte polling, word prior,
farptr xmitbuf, word xmitsize);
Installs the printer driver for the specified port. Ports 0
(LPT1), 1 (LPT2), and 2 (LPT3) are supported.
"port" The output port to use.
If the port number is ORed with 0x80, the port is
relative. This means that the entry in the BIOS
table for LPT-Ports is used to search the tables
internal to the driver for the port information,
instead of using the table entry directly. If the
port address cannot be found, the driver returns
with an error code. Note that ports are numbered
from 0, so to specify LPT1, pass 0x80 as
parameter.
"polling" specifies polling output when nonzero, interrupt
output when zero.
"prior" sets the priority of the printer output task.
"xmitbuf" is a buffer area for the printer output buffer.
May be NULL for automatic allocation.
"xmitsize" specifies the output buffer size in bytes.
The return value is the internal port number for use with
the other driver routines. -1 is returned on error (invalid
port number, bad buffer sizes, nonexistent hardware).
void prt_remove (int port);
Removes the printer driver for the specified port.
Ctask Manual - Version 2.0 - 89-12-21 - Page 75
void prt_remove_all (void)
Removes all installed printer ports. This routine is
automatically called on remove_tasker if ports were
installed.
void prt_change_control (int port, byte control);
Changes the printer control output lines of the port. The
value for "control" may be combined from the following
values defined in "prt.h":
AUTOFEED will enable printer auto feed if set
INIT will initialise (prime) the printer if clear
SELECT will select the printer if set
int prt_write (int port, byte ch, dword timeout);
Write a byte to the printer. A timeout may be given.
Returns 0 on success, TIMEOUT on timeout, WAKE on wake.
int prt_status (int port);
Returns the current printer status lines, combined from the
values
BUSY Printer is busy when 0
ACK Acknowledge (pulsed 0)
PEND Paper End detected when 0
SELIN Printer is selected (on line) when 1
ERROR Printer error when 0
int prt_complete (int port);
Returns 1 if the printer buffer has been completely
transmitted, 0 otherwise.
int prt_wait_complete (int port, dword timeout);
Waits for printer output to complete.
Returns 0 on success, else TIMEOUT on timeout, WAKE on wake.
void prt_flush (int port)
Flushes the output pipe of the printer. Tasks waiting for
print completion are made eligible.
Ctask Manual - Version 2.0 - 89-12-21 - Page 76
Some notes on potential trouble spots
Turbo C console output
As mentioned in the manual, console output should generally be
done only from one task, or be protected with resources. This is
especially true for Turbo C (Version 1.5 and later), because the
putch function is NOT REENTRANT. Since other console output also
uses putch in the end, you have to be extremely careful not to
crash your system by entering the putch routine concurrently. You
should use the "conout" module provided with CTask to channel
console output through a buffer. If you own the Turbo C library
source code, you could replace the "_VideoInt" routine in file
"crtinit.cas" with an (inline) assembler equivalent that pushes
BP on the stack instead of storing it into a static variable.
The timer tick EOI
To allow CTask to run concurrently with other background programs
that might steal the timer tick interrupt, and to enable high
priority tasks to override timer ticks, the tick interrupt
handler issues an EOI before calling the scheduler. Later, the
tick task will (possibly indirectly) call the original BIOS tick
handler, which again issues an EOI for an interrupt that is no
longer pending. Although this should practically not pose any
problems, since no other interrupts could normally be pending at
this time, and the EOI is not stored, it is theoretically
possible for interrupts to be falsely acknowledged (at least
that's what the books say).
If you experience problems in special applications (especially if
interrupt priorities have been reprogrammed), you may use the
IFL_INT8_DIR installation flag. This flag changes the logic in
the timer interrupt handler such that the original interrupt is
called first, and no additional EOI is issued. You could also
replace the timer-chaining logic with a routine that substitutes
the BIOS timekeeping (use the BIOS listing as a reference). This,
however, would preclude chaining to TSR's that have hooked the
timer tick.
Debugging
Debugging CTask programs can be surprising. The debugger can't
know about the task structure of your program, and you can easily
sit and wonder for hours why the debugger is behaving so
strangely until it finally sinks down: the debugger itself can be
preempted.
The keyboard and timer interrupts are the main culprits. Both can
hit during execution of debugger routines, so that data may be
different from what you expect it to be. For example, if you are
Ctask Manual - Version 2.0 - 89-12-21 - Page 77
tracing through code that reads or writes a global variable that
is also modified by another task, you may find that this variable
has a completely different value when you dump it from the value
that was just loaded into a register on the last instruction
trace. If you are using Periscope, be sure to let Periscope
restore the INT 8 and INT 9 entries. This is not exactly pleasant
when you're using a foreign keyboard, but it can avoid a lot of
trouble. If you're debugging at the application level, those
precautions may not be necessary, but if you want to trace into
the kernel routines, you must be extremely careful not to trash
the debugger.
Ctask Manual - Version 2.0 - 89-12-21 - Page 78
Changes from Previous Versions
When upgrading from previous versions, you have to recompile all
modules that use any CTask functions or data structures. Due to
the major changes in the control blocks, just re-linking is not
sufficient.
Changes for CTask 1.2 to 2.0
CTask 2.0 unifies the queue concept, and uses doubly-linked lists
for all queues, including the task and timer queues. Although the
overhead is slightly higher when inserting elements, the overall
logic is greatly simplified, and removing elements from arbitrary
points in a queue is a snap. To avoid a timing penalty for the
new functionality, all low-level queue handling code was
implemented in assembler.
The new concept allows better support for the yield() operation,
and it also allows an improved handling of timeout elements. With
the 1.1/1.2 algorithm, it was not completely safe to process the
timeout queue with interrupts enabled, and changes to the queue
required great care. The new handling of the timeout queue, which
is now sorted, and stores the tick difference to the previous
element instead of an absolute count, decreases the amount of
time spent in the timeout loop. Watch elements, which still
require stepping through all queue elements, have been separated
from timeouts. While the processing of the queue still has to be
done with interrupts disabled, the timeout/watch action now is
completely uncritical.
The changes to the queue structures required changes in nearly
all modules. The main changes were in the tsksub, tsktimer, and
tskasm modules, the tskque module was added.
Interface Changes
Version 2.0 introduces some changes in the interface. Since the
changes only affect installation and previously unavailable
functions, the impact on your programs should be minimal. If
you're using a 1.2 pre-release, watch out for the changes in the
name search and ticker functions. The affected routines are
install_tasker
Two new parameters added. Late 1.2 pre-releases
implemented but ignored the "flags" parameter,
and recommended setting it to zero. This is no
longer true.
Ctask Manual - Version 2.0 - 89-12-21 - Page 79
ctask_resident
New routine to check if CTask is already
resident.
find_name
Name changed from tsk_find_name, values for
"kind" parameter changed.
find_group_name
New routine for 1.1, in 1.2 this was called
tsk_find_group_name. Searches names within a
group. The "kind" parameter values changed.
yield
New routine, schedules with minimal priority.
Version 1.2 also had this, but using it could
lead to task starvation under certain conditions.
get_priority
set_funcs
set_user_ptr
get_user_ptr
New routines to access fields in the TCB.
create_ticker
delete_ticker
set_ticker
get_ticker
New routines for a simplistic time counter. Late
versions of 1.2 had similar functions with
different names, and different parameters.
create_timer
New optional parameter.
create_memory_watch
create_port_watch
New routines to create memory/port watch entries.
Some pre-release versions of 1.2 don't support
the last, optional, user pointer parameter.
wait_memory
wait_port
New routines to wait for memory/port changes.
delete_watch
New routine to delete watch element. Pre-release
versions of 1.2 equated this to delete_timer,
version 2.0 requires delete_watch to be
different.
set_counter
New routine to set counter to given value.
Ctask Manual - Version 2.0 - 89-12-21 - Page 80
v24_flush_transmit
New routine in serial handler to flush transmit
pipe.
prt_flush
New routine in printer handler to flush output
pipe.
timout functions
The timeout function now is passed a parameter,
and is called with interrupts enabled.
assembler interface
All entry points now start with the usual
underline, since the extended language-specific
procedure definitions are used. The "scheduler"
entry was renamed to _tsk_scheduler to avoid
catastrophic results of typos.
internal functions
Previous versions defined most internal CTask
functions as far. This allowed calling them from
outside the CTask kernel (although that was never
recommended). The new version no longer allows
this, since internal functions are near relative
to the common CTask code segment.
Changes for CTask 1.1b to 1.2
The never released version 1.2 added the concept of task groups,
and the save/restore of internal DOS variables, to support
spawning and TSR'ing CTask programs. The TSKDOS module went
through several changes in the pre-release copies.
All relevant global variables of CTask were grouped into a single
structure, and are indirectly accessed through a pointer. This
allows linkage between multiple copies of CTask, with automatic
detection of other copies.
This change required adding new structures, and changing the task
control block to accommodate the new group linkage and the space
for the DOS variable swap.
Also new in 1.2 was the addition of a stack switch on entry to
all interrupt handlers. This change was required to eliminate
problems with TSR's, especially networks, and to support spawned
programs that supply only a minimal amount of stack. CTask now
allocates local stacks from a stack pool to hardware and software
interrupt handlers. On entry to the scheduler, the task registers
are no longer pushed on the stack, but instead are stored in the
task control block. This again mandated a change to the TCB.
Ctask Manual - Version 2.0 - 89-12-21 - Page 81
Support for memory and port watches that check the state of a
memory location or an I/O port on every tick was added.
A "ticker" structure that allows simple timeouts in polling tasks
was introduced.
Task switch save/restore functions were added.
The printer output driver was reworked, an an INT 17 interface
driver has been added. The INT 15 printer output support was
dropped.
Resources to protect concurrent access to INT 10 and INT 13 were
added in the TSKDOS module.
The timer tick interrupt now supports both early and late INT 8
chaining (and the INT9 stuff has finally been renamed to INT8).
This change was required to support network software that would
time out without really having waited long enough if the INT 8
task was postponed, and ticked a number of ticks at once. It can
also avoid incompatibilities with other software that does
strange things in the timer interrupt. Installation of the new
INT 8 handling is optional.
Installation flags now allow on-line customization of some
functions, especially the installation of some of the BIOS
interrupts, and the INT 8 algorithm.
The name-searching functions were augmented to better support
searching local to groups, and searching groups.
The make-files were cleaned up, and the Turbo C make-files
changed to work with Borland's make.
Support for the Turbo C Huge model was added, assembler files now
load the DS register when necessary.
The placement of external definitions in the assembler files was
changed to avoid the fixup errors that appeared when assembling
with TASM instead of MASM (thanks for H.J. Haug for pointing this
out).
All CTask routines are now allocated in a common code segment.
This allows calling local functions with a near call.
All files were polished a bit, with a version number and change
date at the top. Comments have again been added. No warnings
remain for Turbo and Microsoft with all warnings enabled.
All files were affected, and some new files were added.
Ctask Manual - Version 2.0 - 89-12-21 - Page 82
Changes for CTask 1.1 to 1.1b
Release 1.1b fixed a minor bug in tskmain.c which prevented the
main task from being delayed.
Thanks to Kent J. Quirk for reporting the bug.
Release 1.1a was necessary to fix three severe and some minor
bugs in release 1.1. The bugs corrected in 1.1a were
- tskasm.asm: Interrupts enabled too early in the scheduler
(severe)
- tskdos.asm: Registers swapped in DOS version test (severe)
- tsksio.c: Incorrect loop variable in v24_sio_initialise
(severe)
- tskmain.c: Function tsk_dis_preempt missing
- tsksnap.c: Incorrect format specification for continuation
line
- tskasm.asm: Task state incorrect for eligible tasks
Also some minor changes to eliminate warnings, and changes to the
test files so they no longer use concurrent console output.
A console output task was added ("conout.c" and "conout.h") as a
sample for channeling console output through a single task.
Some changes suggested by Stephen Worthington were incorporated
(pipe flush functions, better SIO transmit interrupt handling).
Thanks to Peter Heinrich, Stephen Worthington, and Burt Bicksler
for reporting the bugs.
Changes for CTask 0.1 to 1.1
Thanks to Peter Heinrich, Tron Hvaring, and Dave Goodwin for
their suggestions and the bug reports.
The main changes were
- Support for named control blocks
- Support for dynamic allocation of control blocks and
buffers
- Task state display support (snapshot dump)
- More flexible timer handling, interrupt disable times
reduced
- SIO support extended for shared IRQ's and on-line
definition of ports; save and restore of control
registers
- Bug fixes for known bugs
Ctask Manual - Version 2.0 - 89-12-21 - Page 83
Affected files:
TSKCONF.H - new file, contains configuration options.
TSK.H - additional optional fields in most control
structures
- timer queue structure changed completely
- additional #defines and types for name and timer
control blocks
- tsk_inp, tsk_outp, tsk_dis_int, and tsk_ena_int
functions defined as intrinsics.
TSKMAIN.C - timeout queue handling, and support for timeout
blocks
- system tick interrupt chaining separated from
timeout
- minor changes to accommodate named and dynamic
structures.
TSKSUB.C - changes to the timeout-functions
- new routines for managing named structures.
TSKSIO.C - support for shared IRQ lines an dynamic definition
of ports was added
- complete status of the port is saved before
initialising, and optionally restored on removal of
the driver
- relative port number support added.
TSKPRT.C - relative port number support added.
TSKBUF.C - bug corrections.
TSKALLOC.C - new file, interface to memory allocation functions.
TSKSNAP.C - new file, provides snapshot dump of CTask state.
TSKASM.ASM - Partly runs with interrupts enabled, idle task
removed.
TSKTIM.ASM - cleanup, and changes to accommodate changes in
timeout logic.
TSKBIOS.ASM - new file, handles the AT BIOS INT 15 wait/post
calls.
Minor changes in other files to accommodate named and dynamic
control blocks.
Ctask Manual - Version 2.0 - 89-12-21 - Page 84
Index
AT_BIOS 16
BIOS 20
buffer 44
bufferptr 44
byte 30
byteptr 30
change_timer 55
check_buffer 68
check_counter 63
check_flag 62
check_mailbox 65
check_pipe 67
check_resource 60
check_wpipe 67
clear_counter 63
clear_flag 61
clear_flag_wait_set 62
CLOCK_MSEC 15
CLOCK_MSEC 52
console 20
counter 42
counterptr 42
create_buffer 67
create_counter 62
create_flag 61
create_mailbox 64
create_memory_watch 56
create_pipe 65
create_port_watch 57
create_resource 59
create_task 50
create_ticker 53
create_timer 55
create_wpipe 65
CRITICAL 49
ctask_resident 47
C_ENTER 49
C_LEAVE 49
c_read_buffer 68
c_read_pipe 66
c_read_wpipe 66
c_request_resource 60
c_schedule 48
c_wait_mail 65
c_write_buffer 68
c_write_pipe 66
c_write_wpipe 66
delete_buffer 68
delete_counter 63
delete_flag 61
Ctask Manual - Version 2.0 - 89-12-21 - Index 1
delete_mailbox 64
delete_pipe 66
delete_resource 60
delete_ticker 53
delete_timer 55
delete_watch 58
delete_wpipe 66
DOS 16
DOS 20
dword 30
farptr 30
find_group_name 48
find_name 47
flag 41
flagptr 41
flush_pipe 67
flush_wpipe 67
funcptr 30
F_CRIT 40
F_STTEMP 39
F_TEMP 39
get_priority 51
get_ticker 53
get_user_ptr 52
GROUPS 16
group_rec 40
IBM 16
IFL_DISK 46
IFL_INT15 46
IFL_INT8_DIR 46
IFL_NODOSVARS 46
IFL_PRINTER 46
IFL_VIDEO 45
inc_counter 63
installation flags 45
install_tasker 45
keyboard 20
kill_task 51
mailbox 43
mailboxptr 43
msgptr 43
msg_header 43
NAMELENGTH 36
nameptr 36
namerec 36
pipe 43
pipeptr 43
pipe_free 67
preempt_off 48
preempt_on 48
PRI_INT8 15
PRI_STD 15
PRI_TIMER 15
Ctask Manual - Version 2.0 - 89-12-21 - Index 2
prt_change_control 76
prt_complete 76
prt_flush 76
prt_install 75
prt_remove 75
prt_remove_all 76
prt_status 76
prt_wait_complete 76
prt_write 76
qelem_pri 31
queheadptr 31
queptr 31
queue 31
queue_head 31
read_buffer 68
read_pipe 66
read_wpipe 66
release_resource 60
remove_tasker 46
request_cresource 60
request_resource 60
resource 42
resourceptr 42
restore function 37
save function 37
schedule 48
sched_int 50
send_mail 64
set_counter 63
set_flag 61
set_funcs 52
set_priority 51
set_task_flags 52
set_ticker 53
set_user_ptr 52
SINGLE_DATA 16
spawn 21
start_task 51
stop_task 51
ST_DELAYED 38
ST_ELIGIBLE 39
ST_KILLED 38
ST_RUNNING 39
ST_STOPPED 38
ST_WAITING 38
tcb 38
tcbptr 38
TCMP_CHG 33
TCMP_EQ 33
TCMP_GE 33
TCMP_GES 33
TCMP_LE 33
TCMP_LES 33
Ctask Manual - Version 2.0 - 89-12-21 - Index 3
TCMP_NE 33
TELEM_MEM 32
telem_memwatch 35
TELEM_PORT 33
telem_portwatch 35
telem_timeout 35
TELEM_TIMER 32
TFLAG_BUSY 34
TFLAG_TEMP 34
ticker 41
ticker 53
tick_ptr 41
TIMEOUT 30
Timeout/watch functions 54
Timeouts 52
TKIND_COUNTER 32
TKIND_FLAG 32
TKIND_PROC 32
TKIND_TASK 32
TKIND_WAKE 32
tlink 35
tlinkptr 35
tsk_cli 49
tsk_dis_int 49
tsk_dis_preempt 48
TSK_DYNAMIC 14
tsk_ena_int 49
tsk_ena_preempt 49
tsk_inp 49
TSK_NAMED 15
TSK_NAMEPAR 15
tsk_outp 49
tsk_scheduler 50
tsk_sti 49
TSR 21
TSTAT_CONTWATCH 34
TSTAT_COUNTDOWN 34
TSTAT_IDLE 34
TSTAT_REMOVE 34
TSTAT_REPEAT 34
TSTAT_WATCH 34
TTIMEOUT 30
TWAKE 30
TWATCH 30
TYP_BUFFER 36
TYP_COUNTER 36
TYP_FLAG 36
TYP_MAILBOX 36
TYP_PIPE 36
TYP_RESOURCE 36
TYP_TCB 36
TYP_WPIPE 36
t_delay 54
Ctask Manual - Version 2.0 - 89-12-21 - Index 4
t_keyhit 69
t_read_key 69
t_wait_key 69
user pointer 37
v24_change_baud 71
v24_change_dtr 71
v24_change_parity 72
v24_change_rts 71
v24_change_stopbits 72
v24_change_wordlength 72
v24_check 73
v24_complete 74
v24_define_port 69
v24_flush_receive 74
v24_flush_transmit 74
v24_install 70
v24_modem_status 74
v24_overrun 74
v24_protocol 73
v24_receive 73
v24_remove 71
v24_remove_all 71
v24_send 73
v24_wait_complete 74
v24_watch_modem 72
wait_counter_clear 63
wait_counter_set 63
wait_flag_clear 62
wait_flag_set 61
wait_mail 64
wait_memory 57
wait_pipe_empty 66
wait_port 58
wait_wpipe_empty 66
WAKE 30
wake_task 51
WATCH 30
word 30
wordptr 30
wpipe 44
wpipeptr 44
wpipe_free 67
write_buffer 68
write_pipe 66
write_wpipe 66
yield 48
Ctask Manual - Version 2.0 - 89-12-21 - Index 5