home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The C Users' Group Library 1994 August
/
wc-cdrom-cusersgrouplibrary-1994-08.iso
/
vol_300
/
330_01
/
ctask.doc
< prev
next >
Wrap
Text File
|
1990-10-12
|
306KB
|
7,631 lines
CTask
A Multitasking Kernel for C
Version 2.2 Released 90-10-12
Public Domain Software written by
Thomas Wagner
Ferrari electronic GmbH
Contents
About this Manual 1
Introduction 2
An Example 2
Switching the Context 2
You have Mail 3
Reentrancy and Resources 3
DOS Access 4
Handling the Keyboard 4
Serial I/O and Timeouts 5
Priorities 5
Change to your liking 5
General Notes 7
What can CTask NOT be used for? 7
What is required to use CTask? 7
Do I have to pay for using CTask? 8
What support can I expect? 9
About this Release 10
Multitasking Basics 11
Tasks 11
Events 11
Reentrancy 12
Deadlocks 13
Using CTask 15
Configuration Options 15
Memory Allocation 21
Snapshot 21
Task Stacks 22
Drivers 22
Things to remember 23
Priority Handling 23
Multitasking and DOS 25
Spawning and CTask TSR's 26
Task Groups 27
How does CTask work 29
Queues 29
The Scheduler 30
Events 30
Resources 30
Flags 31
Counters 31
Mailboxes and Pipes 31
Serial and Printer Drivers 32
Ctask Manual - Version 2.2 - 90-10-12 - Contents 1
CTask Data Types 34
Other definitions 35
Typedefs used for simplified type specifications 36
Error return values for event wait functions 37
Queues 37
The timer/watch/hotkey control block 38
The name link structure 43
The task control block structure 43
Task states 46
Task flags 47
The Group Control Block 48
The event control blocks 49
The Ticker structure 49
The flag event structure 50
The counter event structure 50
The resource event structure 51
The mailbox event structure 51
The pipe and word pipe event structure 51
The buffer event structure 52
The call chain structure 53
CTask Routines 54
Global Variables 54
Installation and Removal 54
Searching for names 57
Adding names 58
Remove Functions 58
Preemption and Scheduling 59
Miscellaneous 60
Task Operations 62
Timer Operations 64
Event wait Timeouts 65
"Tickers" 65
Delays 66
Timed Events, Watch Events, and Hotkeys 66
Event Operations 75
Resources 75
Flags 77
Counters 79
Mailboxes 81
Pipes 83
Buffers 86
The Keyboard Handler 88
The Serial I/O handler 88
The Printer Output Driver 94
Support Modules 96
Memory Allocation Interface 96
Printf replacements 97
Snapshot Dump 100
Ctask Manual - Version 2.2 - 90-10-12 - Contents 2
Advanced Topics 101
Primary and Secondary Kernels 101
TSR, Spawning, and EMS 104
Functions called by Kernel Routines 105
Some notes on potential trouble spots 107
Turbo C console output 107
The timer tick EOI 107
Debugging 108
Changes from Previous Versions 109
Changes for CTask 2.1 to 2.2 109
Version 2.2 Interface Changes 111
Changes for CTask 2.0 to 2.1 112
Version 2.1 Interface Changes 115
Changes for CTask 1.2 to 2.0 116
Version 2.0 Interface Changes 116
Changes for CTask 1.1b to 1.2 118
Changes for CTask 1.1 to 1.1b 120
Changes for CTask 0.1 to 1.1 120
Ctask Manual - Version 2.2 - 90-10-12 - Contents 3
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 version 2.0, read the List of Changes,
and the new Advanced Topics section. The chapters on Data Types
and Routines, and the Using CTask section, also contain new
information. All other chapters are basically unchanged.
Version 2.1 users will find little new information here. The
changes in the data structures, and the new routines, are
reflected here, but if you're short on paper, it should be
sufficient to print just 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. The Advanced Topics section is
new.
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 voraussichtlich 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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 1
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
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
Ctask Manual - Version 2.2 - 90-10-12 - Page 2
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
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
Ctask Manual - Version 2.2 - 90-10-12 - Page 3
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
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 4
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.
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
Ctask Manual - Version 2.2 - 90-10-12 - Page 5
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.2 - 90-10-12 - Page 6
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.01 or
later is required for the assembler parts. Conversion to other
compilers is possible if they conform to the new ANSI standard.
Conversion of the assembler parts to other Assembler versions
will likely be more complicated, since it requires substitution
of the simplified model directives by explicit segment
definitions, and adding the DGROUP to offsets referencing data.
Note that you can not use TASM 1.0 to assemble CTask routines.
The first version of TASM was not fully compatible with MASM, and
creates different code in some places, which will lead to fatal
crashes. TASM version 1.01 does not have this problem, and can
safely be used.
Ctask Manual - Version 2.2 - 90-10-12 - Page 7
The CTask modules have been compiled and tested with Microsoft C
6.0 without any apparent problems (Optimization level tested was
/Ox). Since the stability of the MSC 6.0 code generator is
doubtful, the distributed library was compiled with MSC 5.1.
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.
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" and
"IBM" configuration flags in tskconf.h can be disabled to elimi-
nate 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 nor-
mal 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 thor-
oughly 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.2 - 90-10-12 - Page 8
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, 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.
If you don't hear for me for a while, send me a short reminder.
The same goes for UUCP E-mail, since I'm not directly connected.
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
Ctask Manual - Version 2.2 - 90-10-12 - Page 9
once per day, so you can expect relatively fast responses to your
queries.
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
living near a BT Tymnet node can access BIX through international
Tymnet at special low rates. Call the BIX helpline for Tymnet
access points and charges. Other international users will 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, Dave Goodwin,
Chris Blum, and Dan Heine, for their bug reports, suggestions,
and enhancements. Bug reports and suggestions also came in from
others, thanks to all who wrote or called. The serial code in
TSKSIO.C was enhanced for version 2.0 by S. Worthington, and his
ideas on debugging have been incorporated in the version 2.1
debugging mode. Dan Heine provided valuable info on 80x87 numeric
coprocessor support. Chris Blum wrote the integer-based
millisecond to clock-tick conversion routine.
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 and of possible
severe bugs.
Ctask Manual - Version 2.2 - 90-10-12 - Page 10
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 Taskfunc 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.2 - 90-10-12 - Page 11
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.2 - 90-10-12 - Page 12
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.2 - 90-10-12 - Page 13
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.2 - 90-10-12 - Page 14
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. Version
2.1 collects all configuration options for both Assembler and C
in this file.
The entries are
CODE_SHARING
If TRUE, the generated kernel supports code sharing. This
requires the entry points to load DS on entry, and compi-
lation with Large model (MSC) or Huge model (TC).
This option is normally disabled (FALSE).
Ctask Manual - Version 2.2 - 90-10-12 - Page 15
NEAR_CODE
If TRUE, all CTask routines are 'near'. Use only with small
or compact model. You will have to change the make-files so
the compiler model is no longer Large, and the code segment
is not named, when turning on this flag. The default is
FALSE. Setting this option TRUE will save code, and make
calls faster, but the library will no longer be model
independent.
This option is normally disabled (FALSE).
LOCALS_FAR
If TRUE, internal CTask routines ar 'far'. This might be
necessary if your compiler/linker does not allow placing all
CTask kernel code in a common segment. Do not set this flag
if NEAR_CODE is set.
This option is normally disabled (FALSE).
CALL_PASCAL
Use Pascal calling sequence for CTask routines. This may
save some code, but may cause naming conflicts if your
compiler limits external Pascal names to 8 characters.
This option is normally disabled (FALSE).
TC_HUGE
(Assembler only) Define TC_HUGE for use with the Turbo C
Huge model, and if it is desired to separate CTask's data
from the normal data segment. This flag causes the
CTASK_DATA segment to be defined, (class is DATA) and DS to
be loaded with this segment on function entry. The C
functions in the CTask kernel must be compiled to use the
same data segment.
This option is normally disabled (undefined).
LOAD_DS
(Assembler only) Define LOAD_DS to reload the data segment
on entry to global functions. This flag must be defined for
TC_HUGE. It can also be defined if the data segment can not
safely be assumed to be loaded in DS on function entry, for
example if the code sharing feature of version 2.1 is used.
The C routines must be compiled with the necessary compiler
switches or the _loadds keyword in this case.
This option is normally disabled (undefined).
Ctask Manual - Version 2.2 - 90-10-12 - Page 16
ROM_CODE
(Assembler only) Define ROM_CODE TRUE for embedded systems
using ROM-based code. This option disables storing variables
in the code segment in some modules. Note that most DOS-
related modules are not ROMable.
This option is normally disabled (FALSE).
FAR_STACK
(Assembler only) Define FAR_STACK TRUE to save some space in
the default DGROUP. With this define TRUE, the local stacks
for the scheduler, the timer, and the interrupt handlers,
are allocated in a separate segment. With Turbo C, you may
have to edit the "c0.asm" startup module to accomodate the
new segment.
This option is normally disabled (FALSE).
TSK_DYNAMIC
If TRUE, 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 appli-
cations (except if you provide your own memory allocation
routines).
This option is normally enabled (TRUE).
TSK_DYNLOAD
If FALSE, this instance of the kernel does not include
dynamic allocation routines. Setting this flag to FALSE when
TSK_DYNAMIC is TRUE only makes sense with multiple linked
kernels. A resident kernel might not employ dynamic
allocation, whereas a secondary kernel needs it. In this
situation, TSK_DYNAMIC must be set in the primary so that
the task kill code is included and the kernel configurations
match, but TSK_DYNLOAD may be false to prevent inclusion of
the malloc/free run-time routines. This option has no effect
if TSK_DYNAMIC is FALSE. The default is TRUE.
This option is normally enabled (TRUE).
TSK_NAMEPAR
If TRUE, all create_xxx calls accept an additional
parameter, the name of the created control block. This name
should contain 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 (TRUE).
It must be enabled for DOS.
Ctask Manual - Version 2.2 - 90-10-12 - Page 17
TSK_NAMED
If TRUE, all control blocks (except timer control blocks)
are named and linked on creation. This allows the snapshot
dump routine to display the system state, and also allows
linking to control blocks from secondary kernels.
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 (TRUE).
It must be enabled for DOS.
GROUPS
If enabled, task groups, i.e. multiple invocations of CTask,
are supported.
This option is normally enabled (TRUE).
It must be enabled for DOS.
CLOCK_MSEC
If TRUE, 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.
This option is normally disabled (FALSE).
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).
Ctask Manual - Version 2.2 - 90-10-12 - Page 18
PRI_INT8
Determines the priority of the "int8"-task. This task chains
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.
AT_BIOS
If enabled, the AT BIOS wait/post handler is installed. May
be disabled when compatibility problems arise. You can also
disable installation of this handler with an install flag in
the install_tasker call, but using the configuration flag
will save some code. Embedded systems likely will use FALSE.
This option is normally enabled (TRUE).
INT8_EARLY
If TRUE, the timer interrupt always uses early INT8 proces-
sing, regardless of the IFL_INT8_EARLY installation flag.
Setting this flag saves some code and data, but eliminates
some flexibility. Under DOS, timer ticks can be missed under
certain circumstances when set, so DOS based configurations
always should use FALSE. The installation flag has a
slightly different effect, and may be used under DOS with no
adverse effects. Embedded systems likely will always use
TRUE.
This option is normally disabled (FALSE).
INT8_LATE
If TRUE, the timer interrupt always uses late INT8 proces-
sing, regardless of the IFL_INT8_EARLY installation flag.
Setting this flag saves some code, but eliminates some
flexibility. Embedded systems likely will always use FALSE.
This option is normally disabled (FALSE).
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 (TRUE).
Ctask Manual - Version 2.2 - 90-10-12 - Page 19
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 FALSE if you're not so sure, must be
FALSE if GROUPS is enabled. The current version of the DOS
handler requires GROUPS to be used, so it should only be
TRUE for embedded systems.
This option is normally disabled (FALSE).
EMS
If TRUE, EMS support is included. For DOS applications using
spawn or going TSR, this should always be TRUE, unless it is
known that the program will never be executed in an EMS
environment. It must also be TRUE for programs using EMS.
This option is normally enabled (TRUE).
EMS_SAVE_SIZE
Size of the EMS page info save area. For LIM 4.0 drivers,
only four pages are saved, so 16 bytes should be sufficient.
If the partial page map function is not available in the
driver, more space may be required for holding the full page
map.
The value is normally 16.
NDP
If TRUE, 80x87 support is included. If this option is
enabled, and no coprocessor is present, the machine can hang
if the BIOS equipment configuration is set incorrectly.
NOTE: Coprocessor support is not yet tested. Please report
your experiences with this option.
This option is normally disabled (FALSE).
HOTKEYS
If TRUE, keyboard hotkey support is included. Embedded
systems likely will not use hotkeys, and may set this to
FALSE.
This option is normally enabled (TRUE).
CHECKING
Enables pointer and stack checks when TRUE. Checking
pointers and task stacks slows down the system, but it is
highly recommended to enable this option during development.
Only turn off checking after debugging is complete.
This option is normally disabled (FALSE).
Ctask Manual - Version 2.2 - 90-10-12 - Page 20
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
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."
An option is provided in tskalloc.c to compile the module with
the modifications suggested by Joe Urso.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 21
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
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.
If you have the CHECKING option enabled, the snapshot dump will
output the number of unused bytes of stack for each task. This
can help you determine the optimum stack size for your
application.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 22
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. This
is especially true with the new queue structure introduced in
2.0/2.2, where uninitialised control structures will invariably
lead to a system crash. Enable the CHECKING option to trap such
cases.
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.
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
Ctask Manual - Version 2.2 - 90-10-12 - Page 23
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.2 - 90-10-12 - Page 24
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, and with Network drivers
(Invisible Net) 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
Ctask Manual - Version 2.2 - 90-10-12 - Page 25
Sidekick's screen display by 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 in non-enhanced mode. It has been tested to work with
Windows 286 1.2 and 2.1, and with Windows 3.0 in real mode, 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.
CTask 2.2 will not work with Windows 3.0 in 386 enhanced mode
when installed as a TSR before starting Windows. I now know why,
but I did not have the time to do anything about it. The next
version probably will support full Windows 3.0 compatibility. If
you start CTask within Windows, everything will work smoothly,
but CTask instances in different DOS-windows will not be able to
communicate with each other.
Other 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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 26
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
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 27
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.
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.2 - 90-10-12 - Page 28
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 to non-8086
environments.
Ctask Manual - Version 2.2 - 90-10-12 - Page 29
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.2 - 90-10-12 - Page 30
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.2 - 90-10-12 - Page 31
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.2 - 90-10-12 - Page 32
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.2 - 90-10-12 - Page 33
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.2 - 90-10-12 - Page 34
Additionally, you may use the types
byte - for unsigned characters
word - for unsigned short integers
dword - for unsigned long integers
funcptr - for far pointers to void functions
funcptr_void - for far pointers to void functions taking no
parameters
funcptr_int - for far pointers to void functions taking a
single int parameter
funcptr_fp - for far pointers to void functions taking a
single far pointer parameter
funcptr_dword - for far pointers to void functions taking a
single doubleword parameter
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.
Other definitions
The following definitions are used inside CTask to avoid problems
with Microsoft C in some memory models. The standard FP_SEG and
FP_OFF macros will not work correctly in near data models, and
the NULL pointer may not actually be 0 when assigned to a far
pointer. The offsetof operator is not defined for MSC 5.1.
TFP_SEG(ptr) Is a macro to isolate the segment part of
a far pointer.
TFP_OFF(ptr) Is a macro to isolate the offset part of
a far pointer.
TMK_FP(seg,off) Is a macro to make a far pointer out of a
segment and an offset.
LNULL Is a far pointer to nothing.
TSK_OFFSETOFF(struc,field)
Is a macro that returns the byte offset
of "field" within the structure type
"struc".
TSK_STRUCTOP(struc,ptr,field)
Is a macro that returns a pointer to the
start of the structure of type "struc",
given a pointer "ptr" which points to
"field" within the structure.
Ctask Manual - Version 2.2 - 90-10-12 - Page 35
The definition TN is used in CTask starting at version 2.1 to
avoid using extensive #if directives for routines accepting name
parameters. The previous way of enclosing the parameter in #if /
#endif made the sources hard to read. The TN macro will expand to
nothing if TSK_NAMEPAR is FALSE, and to a comma followed by the
parameter if TSK_NAMEPAR is TRUE.
TN(name) Define name parameter if TSK_NAMEPAR is
TRUE.
In addition, a number of definitions are provided to statically
initialise event structures (flags, resources, etc.). Those
definitions (DEF_xxx) are listed in the "routines" section under
the corresponding create_xxx routine entry. You use them in place
of a normal variable definition, i.e. to create an initialised
flag, you would write
static DEF_FLAG (myflag, "MyFlag");
...
add_event_name (&myflag);
instead of
static flag myflag;
...
create_flag (&myflag, "MyFlag");
Typedefs used for simplified type specifications
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;
typedef void (Taskfunc *funcptr)();
typedef void (Taskfunc *funcptr_void)(void);
typedef void (Taskfunc *funcptr_int)(int);
typedef void (Taskfunc *funcptr_dword)(dword);
typedef void (interrupt far *intprocptr)(void);
typedef void far *farptr;
typedef byte far *byteptr;
typedef word far *wordptr;
Ctask Manual - Version 2.2 - 90-10-12 - Page 36
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 or hotkey action.
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 bit 7
(Q_HEAD) being set in the kind field.
#define Q_HEAD 0x80 /* Queue head flag */
typedef struct {
word prior;
word ini_prior;
} qelem_pri;
typedef struct queue_rec far *queptr;
Ctask Manual - Version 2.2 - 90-10-12 - Page 37
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 same
structure is also used to chain name and timeout/watch/hotkey
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;
The timer/watch/hotkey 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, to specify
memory or port locations to be watched for a change or a certain
value on every timer tick, or for keyboard hotkeys.
The "link" field links the active structures into the timer,
watch, or hotkey queue. The kind of the element (which is
specified by the "kind" field of "link") determines the
appropriate queue.
The "chain" field (present only if task groups are enabled) links
all timer structures of a group. This allows automatic removal of
elements on termination.
The "next" field is exclusively used by the timer task to chain
elements that have to be activated.
Ctask Manual - Version 2.2 - 90-10-12 - Page 38
"strucp" points to the structure to be acted upon, "struckind"
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.
#define TKIND_COUNTDEC 6
Strucp points to a counter that should be decreased on
timeout.
The timeout structure is used for timeouts, memory/port
watchpoints, and hotkeys. The union "elem" contains either the
tick reload value, the specs for the memory or port location to
watch, or the hotkey scancode and flag values. "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.
#define TELEM_PORT 0x30
This is a port watch element.
Ctask Manual - Version 2.2 - 90-10-12 - Page 39
#define TELEM_HOTKEY 0x40
This is a keyboard hotkey 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.2 - 90-10-12 - Page 40
Keyboard hotkeys hold the three BIOS keyboard flag comparison
values, and the keyboard scancode, in "elem.key".
The "flags" field holds both permanent and temporary processing
flags:
#define TFLAG_BUSY 0x01
If this bit is set, the timer task is busy processing
this element. It may not be deleted or de/enqueued.
Routines processing timer elements must not modify the
control block except the flag field.
#define TFLAG_ENQUEUE 0x02
The element is to be (re-)enqueued in the appropriate
queue after processing. This is a temporary flag, used
only while TFLAG_BUSY is set.
#define TFLAG_UNQUEUE 0x04
The element is to be removed from its queue after
processing. This is a temporary flag, used only while
TFLAG_BUSY is set.
#define TFLAG_REMOVE 0x08
The element is to be deallocated after processing. This
is a temporary flag, used only while TFLAG_BUSY is set,
for elements with TFLAG_TEMP set.
#define TFLAG_REPEAT 0x40
The element is a repeat element. For timeout elements,
the timeout count is reloaded on activation. Other
elements are re-enqueued without change when activated.
#define TFLAG_TEMP 0x80
If this bit is set, this is a temporary element, which
will be deallocated on activation or delete.
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.2 - 90-10-12 - Page 41
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;
};
typedef struct {
byte mask;
byte compare;
} kbflag;
struct telem_hotkey {
kbflag flag1;
kbflag flag2;
kbflag flag3;
byte scancode;
};
struct tlink_rec {
queue link;
tlinkptr next;
farptr strucp;
dword user_parm;
queue_head chain; (if GROUPS)
union {
struct telem_memwatch mem;
struct telem_portwatch port;
struct telem_timeout time;
struct telem_hotkey key;
} elem;
byte elkind;
byte struckind;
byte flags;
};
typedef struct tlink_rec tlink;
Ctask Manual - Version 2.2 - 90-10-12 - Page 42
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_GROUP 0 Group control block
#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
#define TYP_TIMER 9 timeout element
#define TYP_WATCH 10 memory/port watch element
#define TYP_HOTKEY 11 hotkey element
The head element of the name list (normally the group control
block) has the Q_HEAD bit in its kind field set.
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
Ctask Manual - Version 2.2 - 90-10-12 - Page 43
value. If variable priority is enabled, "prior" is incremented on
each scheduler call, and reset to "ini_prior" when the task is
activated.
"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,
and is used by the scheduler and the snapshot dump module if
checking is enabled to check for stack overflow and 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, into the watch
queue if the task is waiting for a watch event, or into one of
the hotkey queues if the task is waiting for a hotkey. 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 Taskfunc funcname (tcbptr tcb);
The functions are called with a pointer to the current TCB, DS is
set up to point to the TCB data segment. Interrupts are enabled.
The stack is the scheduler's local stack, so the routine must not
use excessive local stack space. Calling CTask-routines that
might cause the scheduler to be activated must be avoided. See
the "Advanced Topics" chapter for more information.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 44
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. The same goes
for "sched_ent_func" which holds a function pointer to an
internal function of the DOS module when a task is active in DOS.
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.
The "ems_map" and "ndpsave" fields hold the EMS and numeric
coprocessor state, and are only present if the respective options
are enabled.
typedef struct tcb_rec far *tcbptr;
struct tcb_rec {
queue cqueue;
queheadptr qhead;
byteptr stkbot;
volatile byte state;
byte flags;
byteptr stack;
word t_ax;
word t_cx;
word t_dx;
word t_si;
word t_di;
word t_bp;
word t_es;
word t_ds;
tlink timerq;
volatile farptr retptr;
volatile int retsize;
funcptr save_func;
funcptr rest_func;
farptr user_ptr;
gcbptr group; (if GROUPS)
gcbptr homegroup; (if GROUPS)
funcptr sched_ent_func; (if DOS)
volatile byte indos; (if DOS)
volatile byte new; (if DOS)
word base_psp; (if DOS)
dword psp_sssp; (if DOS)
byte swap_area [DOSSWAPSIZE]; (if DOS)
namerec name; (if TSK_NAMED)
byte ems_map [EMS_SAVE_SIZE]; (if EMS)
ndpsave_t ndpsave; (if NDP)
};
Ctask Manual - Version 2.2 - 90-10-12 - Page 45
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 included
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.
#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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 46
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 (), or return
-> stopped by delay (0)
-> delayed by delay (n != 0)
-> eligible by scheduler
-> waiting by wait_xxx ()
waiting -> killed by kill_task (), or event deletion
-> 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.
#define F_PERM 0x20
This is a "permanent" system task, it may not be killed.
Ctask Manual - Version 2.2 - 90-10-12 - Page 47
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.
#define F_USES_NDP 0x02
This task uses the numeric coprocessor. The NDP state
must be saved/restored on a task switch. Do not set
this flag if no coprocessor is installed.
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.
"main_ptr" points to the "main" task of this group, i.e. the task
designated by a NULL tcb pointer.
"remove" holds the last element in a chain of remove functions.
This function is called whenever the group is uninstalled.
The "telem_list" and "ticker_list" are the heads for the timer
element and ticker lists. Timers and tickers are chained into
this list to allow automatic removal on group termination.
The "palloc" and "pfree" functions hold pointers to the
allocation functions tsk_alloc and tsk_free. The functions are
accessed through group-relative pointers to allow code sharing
between invocations.
Ctask Manual - Version 2.2 - 90-10-12 - Page 48
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;
tcbptr main_ptr;
callchainptr remove;
queue_head telem_list;
queue_head ticker_list;
#if (TSK_DYNAMIC)
farptr (Globalfunc *palloc)(word);
farptr (Globalfunc *pfree)(farptr);
#endif
};
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.
"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. If task groups are enabled, an additional "chain"
field links all ticker structures of a group to allow automatic
removal on group termination.
Ctask Manual - Version 2.2 - 90-10-12 - Page 49
typedef struct ticker_rec far *tick_ptr;
typedef struct ticker_rec {
tick_ptr next;
dword ticks;
queue_head chain; (if GROUPS)
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;
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;
Ctask Manual - Version 2.2 - 90-10-12 - Page 50
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;
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 51
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;
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.2 - 90-10-12 - Page 52
The call chain structure
The call chain structure is used to link "remove" functions into
a chain. This chain is processed when a task group, or the CTask
kernel, is removed. It contains a single forward link, and a
pointer to the remove function. It also contains a user defined
value that can be used by the called remove function.
typedef struct callchain_rec far *challchainptr;
typedef void (Taskfunc *funcptr_ccp)(callchainptr);
typedef struct callchain_rec
{
callchainptr next;
funcptr_ccp func;
farptr user_ptr;
byte flags; (if TSK_DYNAMIC)
} callchain;
Ctask Manual - Version 2.2 - 90-10-12 - Page 53
CTask Routines
Global Variables
int ctask_active
Contains 1 if CTask was installed, 0 before the call to
install_tasker and after the call to remove_tasker.
int tsk_use_ndp
Is set to 1 by install_tasker if the NDP configuration
option is TRUE and a numeric coprocessor is installed. This
variable is used by create_task to determine whether to set
the F_USES_NDP task flag. If tsk_use_ndp is nonzero during
the call to create_task, the flag is set, causing the NDP
state to be saved and restored for this task. You can change
this variable to 0 before calling create_task to avoid this.
You can also turn on or off the flag individually with
tsk_set_flag, but it is usually safer to use the
tsk_uses_ndp variable. Setting the F_USES_NDP flag through
tsk_set_flag when no coprocessor is present may hang the
system.
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 except for secondary kernels. 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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 54
"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.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 55
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.
IFL_NOEXITCHECK
Don't check for premature exit if set. This
flag may be set to disable the standard check
for program exit without proper CTask
termination. It may be useful in conjunction
with MS Windows programs using CTask, since
the Windows logic may fool the normal exit
check.
"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.
int ctask_resident (void);
This routine allows checking if another copy of CTask is
already resident. It must 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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 56
int link_ctask (void);
This routine checks if another copy of CTask is resident,
and verifies that the resident copy has a routine stub table
to allow code sharing. It must be called before
install_tasker is invoked.
Returns 1 if a code-sharing copy of 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, LNULL 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.2 - 90-10-12 - Page 57
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.
Adding names
void add_event_name (nameptr elem);
Task and event names are normally linked into the name chain
automatically on creation. Statically initialised event
blocks (events defined with DEF_xxx macros) do not need the
create_xxx call, but you have to call add_event_name to link
the structure into the name chain.
"elem" is a pointer to the "name" field in the event
control block.
Example:
void init_my_flag (void)
{
static DEF_FLAG(my_flag, "MyFlag");
add_event_name (&my_flag.name);
}
Remove Functions
callchainptr chain_removefunc (funcptr_ccp func,
callchainptr chain,
farptr user_ptr);
Chain a "remove" routine. The remove routine chain is called
whenever the group or CTask is removed. It may be used for
clean-up work, for example to un-install interrupt handlers.
The function enters the routine address passed as parameter
into the call chain control block, and chains the block into
the remove chain. If dynamic allocation is enabled, the
chain pointer may be LNULL.
The routine returns the chain pointer, or LNULL if the block
could not be allocated.
Ctask Manual - Version 2.2 - 90-10-12 - Page 58
Installation example:
callchain remchain;
chain_removefunc (my_remove_func, &remchain, NULL);
The remove routine should have the following form:
void Taskfunc my_remove_func (callchainptr chain)
{
... /* cleanup */
}
For more information, refer to the "Advanced Topics" chapter.
void unchain_removefunc (callchainptr chain);
Unchain a remove routine from the remove chain.
Preemption and Scheduling
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 tsk_dis_preempt (void)
Temporarily disable task preemption. Preemption will be re-
enabled by tsk_ena_preempt or an unconditional scheduler
call.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 59
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.
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. This entry should be used by interrupt
handlers to cause a reschedule. Interrupt handlers should
not use _tsk_schedule.
Miscellaneous
int tsk_dis_int (void)
Disable interrupts. Returns the state of the interrupt flag
prior to this call (1 if interrupts were enabled).
Ctask Manual - Version 2.2 - 90-10-12 - Page 60
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.2 - 90-10-12 - Page 61
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. If the NDP
option is enabled, the F_USES_NDP flag is set in the tcb
when the NDP is installed and the global variable
tsk_use_ndp is nonzero.
Returns a pointer to the created tcb, or LNULL on error.
"task" is a pointer to a tcb (LNULL 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 (LNULL
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.
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 LNULL for "task" will start the "main
task".
Ctask Manual - Version 2.2 - 90-10-12 - Page 62
int stop_task (tcbptr task);
Stops a task, i.e. removes it from all queues. A value of
LNULL 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 LNULL 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 LNULL for "task" will set the
priority of the "main task".
word get_priority (tcbptr task)
Returns the initial priority of the specified task. A value
of LNULL for "task" will get the priority of the "main
task".
void set_task_flags (tcbptr task, byte flags)
Sets the flags of the task to "flags". The only flags that
can be changed are
F_CRIT the task can not be preempted if set
F_USES_NDP the task uses the numeric coprocessor
Note that you may NOT modify the flag field in the tcb
structure directly. A value of LNULL for "task" will set the
flags of the "main task".
Ctask Manual - Version 2.2 - 90-10-12 - Page 63
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 LNULL for "task" will set the
functions of the "main task". To deactivate one or both
functions, pass LNULL as function parameter.
See the "Advanced Topics" chapter for more information.
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 LNULL 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
LNULL for "task" will get the user pointer of the "main
task".
tcbptr curr_task (void);
Returns the pointer to the TCB of the currently running
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.
word ticks_per_sec (void);
Returns a rough estimate of the number of ticks per second
even if CLOCK_MSEC is not enabled.
Ctask Manual - Version 2.2 - 90-10-12 - Page 64
Event wait Timeouts
When waiting for an event, a timeout may be specified. The ope-
ration will terminate with an error return value if the timeout
is reached before the event occurs. NOTE that the timeout para-
meter is not optional. To specify no timeout, use the value 0L.
"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 LNULL, a new ticker element
will be created.
Returns the ticker element pointer, LNULL 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.2 - 90-10-12 - Page 65
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, Watch Events, and Hotkeys
You can create timer control blocks, memory/port watch blocks,
and keyboard hotkey blocks, to
- set a flag
- increment or decrement 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.
Keyboard hotkeys are checked in the keyboard hardware interrupt.
They allow checking for key scan codes and/or keyboard state flag
combinations.
See the "Advanced Topics" chapter on restrictions placed on
functions called by timer/watch/hotkey conditions.
Ctask Manual - Version 2.2 - 90-10-12 - Page 66
tlinkptr create_timer_elem (tlinkptr elem, dword tout,
farptr strucp, byte kind,
byte rept [,dword userpar]);
Creates a timer control block, but does not activate it.
Returns the address of the control block, LNULL on error.
"elem" is the control block to initialise (LNULL for
automatic 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
in/decreasing 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.
TKIND_COUNTDEC for counter decrement.
"rept" if nonzero, the action will be repeated on every
"tout" timeout interval.
"userpar" is a doubleword value that can be used by the
timeout function if kind is TKIND_PROC. This
parameter is optional.
NOTE: Timer control blocks can not be named.
tlinkptr create_timer (tlinkptr elem, dword tout, farptr strucp,
byte kind, byte rept [,dword userpar]);
Creates a timer control block and activates it.
Returns the address of the control block, LNULL on error.
See create_timer_elem above for parameters. If the timeout
is zero, the control block is initialized, but is not
entered into the timer chain. The timeout must be changed
with change_timer for the timer control block to be
activated.
Ctask Manual - Version 2.2 - 90-10-12 - Page 67
void enable_timer (tlinkptr elem);
Activates a timeout element. This call is ignored if the
timeout value in the control block is zero.
void disable_timer (tlinkptr elem);
Disables a timeout element. The element is not deleted.
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 disable_timer.
void delete_timer (tlinkptr elem);
Deletes a timer control block, and removes it from the
timeout queue.
tlinkptr create_memory_watch_elem (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. The watch is not activated.
Returns the watch control block pointer, or LNULL on error.
"elem" is the control block to initialise (LNULL 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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 68
"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
in/decreasing 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.
TKIND_COUNTDEC for counter decrement.
"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.
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 and activates it.
Returns the watch control block pointer, or LNULL on error.
See create_memory_watch_elem for parameters.
Ctask Manual - Version 2.2 - 90-10-12 - Page 69
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_elem (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 comparison value. The value read is either a word
or a byte, depending on the "in_word" parameter. The watch
is not activated.
Returns the watch control block pointer, or LNULL on error.
"elem" is the control block to initialise (LNULL 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.2 - 90-10-12 - Page 70
"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
in/decreasing 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.
TKIND_COUNTDEC for counter decrement.
"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.
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 and activates it.
Returns the watch control block pointer, or LNULL on error.
See create_port_watch_elem for parameters.
Ctask Manual - Version 2.2 - 90-10-12 - Page 71
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 enable_watch (tlinkptr elem);
The specified watch element is activated.
void disable_watch (tlinkptr elem);
The specified watch element is deactivated. The element is
not deleted.
void delete_watch (tlinkptr elem);
The specified watch element is removed.
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.
tlinkptr create_hotkey_elem (tlinkptr elem,
byte scancode,
byte mask1, byte flag1,
byte mask2, byte flag2,
byte mask3, byte flag3,
farptr strucp, byte kind,
byte rept
[, dword userpar]);
Creates a keyboard hotkey control block. Keyboard hotkeys
compare the scancode and/or the BIOS keyboard flags against
the given values. The flag fields are masked before
comparing. The hotkey is not activated.
The file "kbd.h" contains definitions for scan codes and
flag byte values.
Ctask Manual - Version 2.2 - 90-10-12 - Page 72
Returns the hotkey control block pointer, or LNULL on error.
CAUTION: In contrast to the timer/watch creation functions,
this function does not accept a LNULL parameter for
the element to create. Dynamic allocation can not
be used in an interrupt handler, and a temporary
element would have to be released in the keyboard
interrupt. Therefore, the hotkey control block must
always be static.
"elem" is the control block to initialise. This pointer
must NOT be LNULL.
"scancode" is the keyboard scancode to look for. If scancode
is 0, only the flag values are compared.
"flag1..3" are the values the BIOS keyboard flags are to be
compared with.
"mask1..3" are bitmasks to mask out irrelevant bits before
comparing the flag values. Set both mask and flag
to 0 to disable comparison.
"strucp" points to the structure to be used. This is a
flagptr for setting a flag, a counterptr for
in/decreasing 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.
TKIND_COUNTDEC for counter decrement.
"rept" if nonzero, the action will be repeated whenever
the hotkey is activated.
"userpar" is a doubleword value that is passed to the
hotkey function if kind is TKIND_PROC. This
parameter is optional.
NOTE: Hotkey control blocks can not be named.
Ctask Manual - Version 2.2 - 90-10-12 - Page 73
tlinkptr create_hotkey_entry (tlinkptr elem,
byte scancode,
byte mask1, byte flag1,
byte mask2, byte flag2,
byte mask3, byte flag3,
farptr strucp, byte kind,
byte rept
[, dword userpar]);
Creates a keyboard hotkey control block and enables it.
Returns the hotkey control block pointer, or LNULL on error.
See create_hotkey_elem for parameters.
int wait_hotkey (byte scancode,
byte mask1, byte flag1,
byte mask2, byte flag2,
byte mask3, byte flag3)
Delays the current task until the specified hotkey is hit.
Parameters are the same as in create_hotkey_entry. No
timeout can be specified.
Returns WATCH if the hotkey was hit, WAKE if the task was
activated by wake_task.
void enable_hotkey (tlinkptr elem);
The specified hotkey element is activated.
void disable_hotkey (tlinkptr elem);
The specified hotkey element is deactivated. The element is
not deleted.
void delete_hotkey (tlinkptr elem);
The specified hotkey entry is removed.
Ctask Manual - Version 2.2 - 90-10-12 - Page 74
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, LNULL on error.
"rsc" is the resource control block (LNULL 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.2 - 90-10-12 - Page 75
DEF_RESOURCE(var,name)
This macro defines and initialises a resource control block
at compile time. It can replace the run-time call to
create_resource. However, the created block is not linked
into the name chain. To make the block visible, you must use
add_event_name. Most compilers will not allow aggregate
initialisers of automatic variables, so you must use this
definition with the static attribute inside routines.
"var" is the variable name to define.
"name" is the name of the resource. It can be a zero
length string, but it must not be LNULL. Since
this is a macro, you must give the parameter even
when TSK_NAMEPAR is disabled.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 76
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.
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, LNULL on error.
"flg" is the flag control block (LNULL 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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 77
DEF_FLAG(var,name)
This macro defines and initialises a flag control block at
compile time. It can replace the run-time call to
create_flag. However, the created block is not linked into
the name chain. To make the block visible, you must use
add_event_name. Most compilers will not allow aggregate
initialisers of automatic variables, so you must use this
definition with the static attribute inside routines.
"var" is the variable name to define.
"name" is the name of the flag. It can be a zero length
string, but it must not be LNULL. Since this is a
macro, you must give the parameter even when
TSK_NAMEPAR is disabled.
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.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 78
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",
"dec_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, LNULL on error.
"cnt" is the counter control block (LNULL 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.2 - 90-10-12 - Page 79
DEF_COUNTER(var,name)
This macro defines and initialises a counter control block
at compile time. It can replace the run-time call to
create_counter. However, the created block is not linked
into the name chain. To make the block visible, you must use
add_event_name. Most compilers will not allow aggregate
initialisers of automatic variables, so you must use this
definition with the static attribute inside routines.
"var" is the variable name to define.
"name" is the name of the counter. It can be a zero
length string, but it must not be LNULL. Since
this is a macro, you must give the parameter even
when TSK_NAMEPAR is disabled.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 80
dword 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.
Returns the new value of the counter.
dword dec_counter (counterptr cnt);
Decrements the counter unless it is already zero. If the
counter is decremented to zero, all tasks waiting for the
zero state are made eligible.
Returns the new value of 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.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 81
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, LNULL on error.
"box" is the mailbox control block (LNULL 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.
DEF_MAILBOX(var,name)
This macro defines and initialises a mailbox control block
at compile time. It can replace the run-time call to
create_mailbox. However, the created block is not linked
into the name chain. To make the block visible, you must use
add_event_name. Most compilers will not allow aggregate
initialisers of automatic variables, so you must use this
definition with the static attribute inside routines.
"var" is the variable name to define.
"name" is the name of the mailbox. It can be a zero
length string, but it must not be LNULL. Since
this is a macro, you must give the parameter even
when TSK_NAMEPAR is disabled.
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.2 - 90-10-12 - Page 82
farptr c_wait_mail (mailboxptr box);
Reads mail only if mail is available.
Returns LNULL 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, LNULL on error.
"pip" is the pipe/wpipe control block (LNULL for
automatic allocation).
"buf" is a pointer to the pipe buffer.
"bufsize" is the size of the buffer in bytes.
"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.2 - 90-10-12 - Page 83
DEF_PIPE(var,name,buf,bufsize)
DEF_WPIPE(var,name,buf,bufsize)
This macro defines and initialises a pipe/word pipe control
block at compile time. It can replace the run-time call to
create_pipe/create_wpipe. However, the created block is not
linked into the name chain. To make the block visible, you
must use add_event_name. Most compilers will not allow
aggregate initialisers of automatic variables, so you must
use this definition with the static attribute inside
routines.
"var" is the variable name to define.
"name" is the name of the pipe. It can be a zero length
string, but it must not be LNULL. Since this is a
macro, you must give the parameter even when
TSK_NAMEPAR is disabled.
"buf" is a pointer to the pipe buffer.
"bufsize" is the size of the buffer in bytes.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 84
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.
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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 85
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. There is no
static creation macro for buffers.
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, LNULL on error.
"buf" is the buffer control block (LNULL for automatic
allocation).
"pbuf" is a pointer to the buffer.
"bufsize" is the size of the buffer in bytes.
"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.
void delete_buffer (bufferptr buf);
Calling this routine is optional. It will kill all tasks
waiting to read or write messages.
Ctask Manual - Version 2.2 - 90-10-12 - Page 86
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.2 - 90-10-12 - Page 87
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.2 - 90-10-12 - Page 88
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 LNULL 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 LNULL 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. LNULL is returned on
error (invalid port number, bad buffer sizes, nonexistent
hardware, out of memory).
Ctask Manual - Version 2.2 - 90-10-12 - Page 89
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.2 - 90-10-12 - Page 90
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.2 - 90-10-12 - Page 91
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.2 - 90-10-12 - Page 92
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.2 - 90-10-12 - Page 93
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 LNULL 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.2 - 90-10-12 - Page 94
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.2 - 90-10-12 - Page 95
Support Modules
Memory Allocation Interface
The file tskalloc.c contains routines that interface CTask to the
standard C memory allocation routines. Since the CTask kernel is
model independent, it can not use the runtime routines directly.
Also, to avoid crashes, CTask routines allocating memory must use
a resource to channel access to the memory allocator, since the
standard routines are not reentrant. All memory allocation in
tasks should use the CTask allocation functions instead of the
standard ones. See the chapter on memory allocation for more
information.
farptr tsk_alloc (word size);
Replacement function for the standard malloc.
farptr tsk_calloc (word item, word size);
Replacement function for the standard calloc.
farptr tsk_free (farptr item);
Replacement function for the standard free. This routine
always returns LNULL.
farptr tsk_realloc (farptr item, word size);
Replacement function for the standard realloc.
Global variables:
resource alloc_resource;
Is the memory allocation routine access resource. If
you use other allocator routines, or want to allocate
several items in a row, you may enclose the calls to
the standard C functions in calls to request_resource
(&alloc_resource) and free_resource (&alloc_resource).
Ctask Manual - Version 2.2 - 90-10-12 - Page 96
Printf Replacements
Version 2.1 adds several functions that can (at least in most
cases) replace the standard C printf family functions. Apart from
being a lot faster and smaller, the routines do not assume
DS==SS, are reentrant and completely self-contained, and thus can
be used independent of the memory model in use. They use no CTask
kernel functions, and could also be used in non-CTask
applications, for example in Assembler modules, or device
drivers/DLLs for DOS and Windows. The routines support direct
access to the secondary screen in a dual monitor system.
int tsk_sprintf (farptr str, farptr fmt, ...);
int tsk_vsprintf (farptr str, farptr fmt, farptr arg);
Print to string. Replacement for sprintf and vsprintf.
Returns number of character written, not counting the
terminating zero character.
void tsk_rprintf (farptr fmt, ...);
void tsk_vrprintf (farptr fmt, farptr arg);
Output directly to screen regen hardware buffer. Allows
printing to the secondary monitor in dual-screen systems,
and is extremely fast.
void tsk_printf (farptr fmt, ...);
void tsk_vprintf (farptr fmt, farptr arg);
Print to console. Uses INT 10 calls, so it's faster than
standard printf, but can't be redirected.
void tsk_fprintf (int handle, farptr fmt, ...);
void tsk_vfprintf (int handle, farptr fmt, farptr arg);
Print to file. Note that this is no direct replacement for
fprintf/vfprintf, since the parameter is a DOS file handle,
not a FILE *. Use with files opened through the low-level
open functions, or use the standard fileno() function to get
the handle of a stream file. Stream files should be flushed
before use if other C functions were used to write to the
file.
void tsk_putc (char c);
Put character to screen using INT 10.
Ctask Manual - Version 2.2 - 90-10-12 - Page 97
void tsk_puts (farptr str);
Put string to screen using INT 10. A new line is started
after output.
void tsk_rputc (char c);
Put character directly to screen hardware regen buffer.
void tsk_rputs (farptr str);
Put string directly to screen hardware regen buffer. A new
line is started after output.
word tsk_set_dualdis (void);
Initialize the hardware regen output to go to the secondary
monitor. Returns the regen buffer address, or 0 if no
secondary adapter was found.
word tsk_set_currdis (void);
Initialize the hardware regen output to go to the primary
monitor. Returns the regen buffer address, or 0 on error.
void tsk_set_colour (int rows, int cols);
Initialize the hardware regen output to go to the colour
monitor with the given number of screen rows and columns.
void tsk_set_mono (int rows, int cols);
Initialize the hardware regen output to go to the monochrome
monitor with the given number of screen rows and columns.
Ctask Manual - Version 2.2 - 90-10-12 - Page 98
void tsk_set_regen (int regenseg, int port, int rows, int cols);
Initialize the hardware regen output to go to the specified
monitor address. You have to specify
regenseg The segment address of the regen buffer.
port The 6845 display controller port. If zero,
you can still write to the screen, but the
cursor will not be moved.
rows The number of rows on screen.
cols The number of columns on screen.
Note that you can use this function to restrict output to
a smaller part of the screen by increasing the segment
address and decreasing the number of rows.
void tsk_setpos (int row, int col);
Set the cursor position for hardware output. Row and column are
numbered starting at 0.
void tsk_set_attr (int attr);
Set the screen display attribute byte for subsequent hardware
output to "attr".
Global variables:
dword tsk_regen;
word tsk_regen_o;
word tsk_regen_s;
Contain the current hardware screen pointer.
tsk_regen_o contains the current offset, tsk_regen_s
the regen buffer segment. tsk_regen is a synonym for
doubleword access to both variables.
word tsk_disport;
The hardware 6845 controller port address. If set to 0,
the display cursor will not be updated.
Ctask Manual - Version 2.2 - 90-10-12 - Page 99
Snapshot Dump
The snapshot dump module TSKSNAP.C provides a display of the
current state of tasks and control structures. This display is
rather extensive, and can easily exceed screen capacity. It may
be useful to change the routines to suit your needs by omitting
some of the information, or displaying other than the standard
info.
Since display of the snapshot runs at high priority, the system
will slow down noticeably if snapshots are output with high
frequency.
The standard routines provided are
void screensnap (int rows);
Output to hardware screen regen buffer. The hardware screen
must have been initialised with a call to the
tsk_set_dualdis or similar functions (see description of
printf replacements). If the "rows" parameter is nonzero,
the display will be truncated at the given number of rows.
void csnapshot (void);
Output to console using INT 10.
void snapshot (FILE *f);
Output to the given stream file.
Ctask Manual - Version 2.2 - 90-10-12 - Page 100
Advanced Topics
Primary and Secondary Kernels
Version 2.0 officially introduced support for CTask programs
going TSR and spawning. Version 2.1 adds some features to make
this safer (especially in EMS systems), and to save code in
secondary applications.
The manual often refers to the terms "primary" and "secondary"
kernel. The primary kernel is the first installation of CTask.
This kernel installs all basic interrupt handlers, the DOS and
keyboard support, and the system tasks (timer, killer). It basi-
cally is nothing else than a normal, stand-alone CTask
installation.
A secondary kernel is installed by a CTask program run "on top"
of the primary. This, naturally, is only possible if the primary
program has gone resident (TSR), or has spawned another program
(for example COMMAND.COM). A secondary kernel automatically de-
tects that a primary copy of CTask is already installed, and does
not attempt to re-install the interrupt handlers and system
tasks. Instead, it uses a pointer to the global variable block in
the primary to access all global CTask data. This results in both
CTask copies cooperating such that all events, tasks, and other
structures, are common to both invocations, and share all global
resources. The names of control structures are visible accross
the copies, so that a secondary can access control blocks of the
primary, and vice versa.
There is a separate control structure, the "group control block",
for every invocation (or "task group"). This block links all con-
trol structures created by tasks inside the group together, so
that the system can remove those tasks and events from the system
that belong to the secondary, should the secondary terminate
without cleaning up properly. The group control block additio-
nally contains pointers to data structures and routines that can
not be shared (like the memory allocation routines). The group
control block is a static structure that is initialised when you
call install_tasker.
This primary/secondary structure can be completely invisible if
you run CTask programs that could also run as standalone CTask
applications. But the automatic data sharing also makes it
possible to create multi-level programs, which use a resident
part to handle hardware interrupts (for example a com port) in
the background, and an independently loadable program (which
might even be a MS Windows program or driver) to control the
resident part. If the controlling (secondary) program does not
itself use multitasking features (i.e. create tasks), there is no
need for it to include a full-fledged CTask kernel with all the
features, and no need to create a task group. But even if it
creates tasks, but will never use the full CTask features if
Ctask Manual - Version 2.2 - 90-10-12 - Page 101
there is no background kernel, it will not need to include the
code for the basic (primary) interrupt handlers and tasks.
CTask 2.1 includes several options for cooperating CTask appli-
cations to save code in a secondary program. With all these
options, the generated program will not be able to use CTask if
there is no primary kernel installed.
The first option is to use the "stskmain" object created by
compiling tskmain.c with SECONDARY defined. With this define, the
install_tasker and remove_tasker routines will only reference the
second-level group creation routines. All references to the
primary task installation routines, which in turn reference the
interrupt handlers and system tasks, are eliminated.
Install_tasker returns an error code if there is no primary
kernel installed, but all other aspects of CTask will work as
usual. A task group is created.
The second option is to not call install_tasker at all in the
secondary. You need not link either tskmain or stskmain. Calling
"ctask_resident" will establish the data link. This link enables
CTask to work without a task group, such that everything you do
in the secondary appears as if it were executed in the primary
kernel. This, however, means that you should not create any CTask
control structures in the secondary (or at least you should not
allow them to be linked into the name chain). If the secondary
terminates without removing all control structures and tasks it
created, the pointers chained into the primary links will point
to some other program loaded later, which may be destroyed if any
action causes those pointers to be referenced. You also may not
use dynamic allocation in the secondary, since this would try to
allocate memory in the primary applications data space.
The third option is to use code sharing with the "stskmain"
object. Code sharing requires the primary kernel to be compiled
with the CODE_SHARING and LOAD_DS configuration options set to
TRUE. With Microsoft C, setting those options in tskconf.h is
sufficient. With Turbo C, which does not have the "_loadds"
keyword, the primary must be compiled in Huge memory model, and
the TC_HUGE option must be TRUE. The reason for this is that the
routines can now be called from an application with completely
different segment setup. If a routine in the primary kernel is
called from a secondary, the data segment register will point to
the secondary's data segment. The offsets to CTask structures in
the primary code will point to some other data in the secondary.
So all routines that could be accessed from the outside must set
up the data segment register to point to the primary data.
Code sharing uses a "stub table" and an "entry table" for a
primitive form of run-time linking. The entry table simply
contains the addresses of all global routines present in the
primary in fixed order. The stub table contains short "stub"
routines that access the entry table through the global variable
Ctask Manual - Version 2.2 - 90-10-12 - Page 102
pointer, and jump to the address corresponding to the called stub
routine. Both tables are generated from one source,
"tskstub.asm". You must first decide which routines should be
included in the primary, and edit the stub definitions in this
file accordingly. The file is then assembled twice. The copy to
be included with the primary is assembled with GEN_JTAB defined
(this is automatically done by the standard makefile). The stub
table copy for the secondary is assembled without this define.
Install_tasker returns an error code if there is no primary
kernel installed, but all other aspects of CTask will work as
usual. The link_ctask routine can be used before calling
install_tasker to verify that the primary kernel is installed and
has code sharing enabled. A task group is created. The primary
kernel will be slightly larger due to the additional code
required to load the data segment on function entry, but the
secondary will need to link only those CTask routines that are
not defined as entry stubs.
The fourth option is to use code sharing with no call to
install_tasker. This requires nearly zero code in the secondary,
but the same restrictions mentioned with the second option apply.
There is one restriction on cooperating applications, whether
they use code sharing or not: The control structures for both
primary and secondary must be identical. This requires that the
following configuration options are identical on compilation of
both kernels:
TSK_DYNAMIC
TSK_NAMED
EMS
NDP
HOTKEYS
IBM, DOS, GROUPS (must be TRUE for groups to work at all)
If you change those options, the sizes of the global structures,
and the location of some fields within them, change, making it
impossible to share structures. Linking two kernels with
different configurations will not work.
You may, however, change all other configurations. It is
perfectly legal to compile the primary in large model, and the
secondary in small (NEAR_CODE set to TRUE), even if you are using
code sharing. The secondary does not have to set the CODE_SHARING
option, this option is only required in the primary.
Ctask Manual - Version 2.2 - 90-10-12 - Page 103
Secondary application link examples (Microsoft C Large model):
Option 1: link myprog+stskmain,,,ctaskms+ctsupmsl/NOE
Option 2: link myprog,,,ctaskms+ctsupmsl
Option 3: link myprog+stskmain+tskstub,,,ctaskms+ctsupmsl/NOE
Option 4: link myprog+tskstub,,,ctaskms+ctsupmsl/NOE
Note that the "tskstub" object linked with options 3 and 4 is the
one assembled without the GEN_JTAB define. It is not the
tskstub.obj created by the standard makefile.
The /NOE switch is required for the Microsoft linker, since the
entry points in stskmain and tskstub duplicate entries in the
standard CTask library.
TSR, Spawning, and EMS
If you are writing a CTask application that should stay resident
or spawn DOS, you will be hard pressed to reduce the space
required to a minimum. One option to consider is replacing the
standard startup code. This code calls a number of routines to
setup parameters and the environment copy, and requests a lot of
memory, that most likely will never be used by your program. The
source for the startup code is included with MS and Turbo C.
Another space saver is the included printf replacement. If you
don't need the floating point output, tsk_printf and its cousins
give you all the capabilities you'll normally need at greatly
reduced space (if you need printf at all in your TSR).
In general, memory allocation is not needed in a TSR or spawning
program, since you can't allocate once you are resident.
Ommitting the memory allocation routines saves another chunk of
code. The TSK_DYNLOAD define may be set to FALSE to prevent
reference to the memory allocator routines from the CTask kernel
even when TSK_DYNAMIC is TRUE to support secondary kernels.
Again, you may have to change the C startup code to prevent it
from calling routines that allocate memory.
The final option is putting some of your code and/or data in EMS
memory. CTask 2.1 switches EMS on a task basis, and saves the EMS
configuration when a task is created. It would thus be possible
to move tasks and their local data into an EMS bank (in the 64k
LIM 3.2 page frame), and call create_task with the required page
configuration activated. Whenever the task is scheduled in, the
EMS page will have been switched correctly. The only restriction
is that the task control block itself, and all CTask data
structures (event control blocks etc.) must be in common memory.
All other data, and even the task's stack, may be in EMS
(although having stacks in EMS is generally not recommended).
This may be hard to do with C programs, but could possibly be
achieved with the use of overlay techniques.
Ctask Manual - Version 2.2 - 90-10-12 - Page 104
Functions called by Kernel Routines
User-defined functions called from the kernel are the task
save/restore functions, the functions activated with timers,
watches and hotkeys, and the "remove" functions. Some
restrictions apply to those functions.
For calling functions on a timeout, hotkey, 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 area is the area of the timer task, which is
relatively small. Do not use large automatic 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/watch/hotkey
function is
void Taskfunc funcname (tlinkptr elem)
The function is called with a pointer to the timer control block.
The user parameter passed to the create_xxx routines is
accessible through this pointer (elem->user_parm). The data
segment is set to the segment address of the timer control block.
Hotkey functions are equal to timeout/watch functions, but they
are called from inside the keyboard hardware interrupt. The stack
is a local interrupt stack, and the function must not use any
CTask routines that could cause a task to be made waiting or to
be killed.
Remove functions may be called at a time when the state of the
system is unstable, i.e. during the "emergency exit". To avoid
problems, you should not call any DOS functions, and also refrain
from using CTask functions. Remove functions should only be used
to uninstall interrupt handlers and do similar cleanups. The
functions are called with a pointer to the call chain block, and
the data segment is set to the segment address of this block. The
definition is
void Taskfunc funcname (callchainptr chain)
Two functions may be set in the TCB that are called whenever the
task defining them is scheduled out (the "save" function) or
scheduled in (the "restore" function). The main use for those
functions is to save and restore static state variables that are
shared among tasks. A windowing package, for example, may have
Ctask Manual - Version 2.2 - 90-10-12 - Page 105
global variables describing the current window. If you want to
output to different windows from different tasks, you may have to
save the current state whenever the task changes. A better way
than to copy a number of variables would be to make the windowing
package "CTask aware", for example by letting it reference those
variables through the user pointer in the TCB. This solution,
however, may not be feasible with third-party libraries. That's
where the save/restore functions come into play. Both functions
are defined as
void Taskfunc funcname (tcbptr tcb)
i.e. they do not return a value, and receive a pointer to the
current TCB (the task control block defining the function). The
data segment register is set to the segment address of the TCB.
The data segment that all those functions are entered with is not
necessarily the same segment as the normal data segment of your
program. Especially with far data memory models in Microsoft C,
the huge model in Turbo C, or dynamically allocated blocks, you
can not rely on the data segment having the correct value, unless
you make sure that the control block is located in the same
segment. To allow you to access global variables from your code,
you should either use the _loadds keyword with Microsoft C, or
compile the routine in a separate module with all data references
defined extern with Turbo C huge model. The safest way is to
collect all data the routine should modify in a single structure,
and pass the address of this structure as the user pointer/user
parameter. If all data you reference is located in the same file
as the control block, it is reasonably safe to assume that this
data is in the same segment, and thus accessible without special
precautions. You may have to take care not to set the "data
threshold" with Microsoft C too low, however, since this may
cause data items in the same file to be placed in different
segments. The same_seg pragma can be used to avoid this.
Ctask Manual - Version 2.2 - 90-10-12 - Page 106
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, some experts believe it is
theoretically possible for interrupts to be falsely acknowledged.
However, according to the INTEL documentation of the 8259
interrupt controller, the additional EOI can not cause any harm.
What the EOI does is to reset the highest priority ISR (Interrupt
under Service Register) bit in the PIC. According to Intel's
specs, it will never dismiss an interrupt that is not being
serviced, since only the INTAK sequence will cause the ISR to be
set in the first place. If there is an interrupt under service,
the scheduler is not invoked, so the timer chain can't issue an
EOI whilst in the middle of another interrupt. So I think it's
safe to issue multiple EOIs, and I haven't had any adverse
effects with INT8_LATE enabled, even while running serial comms
with 19200 bps on a networked machine (which should generate a
lot of INTs).
If you experience problems in special applications anyway
(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.
Ctask Manual - Version 2.2 - 90-10-12 - Page 107
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
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.2 - 90-10-12 - Page 108
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 changes in the control blocks, just re-linking is not
sufficient.
Changes for CTask 2.1 to 2.2
Version 2.2 mainly is a maintenance release. Several bugs, some
of them severe, were introduced into 2.1 by a number of last-
minute changes. Most bugs were reported by S. Worthington.
Bugs fixed include
- The Microsoft C 5.1 and 6.0 compilers generated incorrect
code for the va_arg macro used to access the optional user
parameter in the create_timer/watch/hotkey routines. The
definition of the last parameter before the "..." as "byte"
caused MSC to access an incorrect (odd) offset for the
parameter. The "rept" parameter for the create_xxx calls has
been redefined as "int" to circumvent this.
- Enabling the interrupts early in the scheduler could lead to
race conditions if a task was made waiting, but then made
runable before the scheduler had enqueued it. This could
lead to unpredictable behaviour. The fix eliminates the race
condition by enqueueing the task into the appropriate queue
immediately, instead of waiting for the scheduler to do it.
- The keyboard handler installation caused problems with some
older BIOSes, since it assumed too easily that an extended
keyboard was present. This caused keyboard input to hang on
some machines with non-extended keyboard BIOS. The check has
now been made more robust.
- The hotkey detection logic would cause an immediate schedule
if a hotkey match occurred, without checking for other
active interrupts. This could lead to problems if the
keyboard interrupt had interrupted another, timing critical,
interrupt. The interrupt active check has been added.
- The "CALL_PASCAL" configuration option did not work. Half of
the implementation was missing in the Assembler parts. This
has been corrected, with Assembler routines using a slightly
different procedure header, and different public/external
pseudo-ops. Assembler routines now can be defined with
Pascal calling sequence, and the test file "minres.asm" has
been updated to use the new definitions.
Ctask Manual - Version 2.2 - 90-10-12 - Page 109
- The TC_HUGE option would not work when given in the
assembler command line only, without changing the LOAD_DS
option. Also, the ctaskh.tc make-file had an incorrect
dependency, causing the tskint17 module to be omitted from
the assembly. Both errors have been corrected, you can again
specify TC_HUGE without editing tskconf.h. The change also
eliminates pass dependency warnings with Tasm 2.0.
- Some key definitions in "kbd.h" were incorrect, and some
were missing. This has been corrected.
- The tsk_switch_stack routine used an incorrect DS in several
places if the ROM_CODE option was defined TRUE. This has
been fixed, the ROM_CODE option now works as expected.
- If a task waiting for a memory/port watch or hotkey was made
runable by tsk_wake or a similar action, some locations in
the next queue element or the queue header could be
destroyed. The logic to identify timer elements has been
changed to avoid this problem.
- Hotkeys, watches, timeout elements, and tickers created by a
secondary invocation were not automatically removed on
termination. This would lead to crashes is there was no
explicit delete_xxx call. The elements are now chained,
removal is automatic.
- Some routine prototypes were missing in tsk.h, and the
add_event_name function was actually defined as add_name.
- The interface between the t_read_key and t_wait_key
functions and the INT 16 handler in tskkbd.h was using the
wrong DS, which would lead to crashes in secondary
invocations. (Fixed in 2.1a)
- The "ticker" structure was defined differently in tsk.h and
tsk.mac. This would cause problems for programs defining
tickers in Assembler modules. (Fixed in 2.1b)
Other changes:
An optional "checking" mode has been implemented. If CHECKING is
defined TRUE in tskconf.h, all task and event pointers are
checked for validity on entry to task functions and other
strategic points. Although some errors can still go undetected,
most memory overwrites or uninitialised pointers that would cause
a catastrophic crash will now be caught, and the system will stop
with an error message before anything vital is destroyed. The
checking also includes a task stack check, which is performed on
every scheduler entry. The stack of a task is initialized to a
defined pattern (0..FF repeated) on a create_task, and the
scheduler checks for the pattern to be present in the bottom
eight bytes of the current tasks stack. If this pattern was
Ctask Manual - Version 2.2 - 90-10-12 - Page 110
overwritten, the system is stopped. This feature can also be used
to determine the maximum amount of stack actually used by a task.
The tsksnap module will display the number of unused bytes of
stack for all tasks if checking is enabled.
The CLOCK_MSEC option, which allows all timeouts to be specified
in milliseconds rather than timer ticks, has been changed to no
longer require floating point support. The routine to convert
milliseconds into ticks was provided by Chris Blum, who also
supplied some fixes to other modules required to correctly handle
the CLOCK_MSEC option.
Timeout elements, watches, and hotkeys can now be created without
being activated. Separate enable/disable routines are provided to
avoid the overhead involved in creating and deleting elements
when switching hotkey-sets or watches.
If multiple hotkey entries for the same key/flag combination were
created, version 2.1 always activated the first entry entered.
Version 2.2 will activate hotkeys in a round-robin fashion.
The hotkey checking code now uses the keyboard intercept entry in
INT 15 if the BIOS supports it. This should increase portability,
since the routine no longer needs to access the keyboard hardware
ports directly.
Version 2.2 Interface Changes
New functions:
create_timer_elem
create_hotkey_elem
create_memory_watch_elem
create_port_watch_elem
These routines are similar to the old-style create_xxx
routines, but they do not activate the created element.
enable_timer
enable_watch
enable_hotkey
Enable timer/watch/hotkey elements
disable timer
disable_watch
disable_hotkey
Disable, i.e. temporarily suspend, timer/watch/hotkey
elements. This is different from the delete_xxx calls,
which will release dynamically allocated elements.
Ctask Manual - Version 2.2 - 90-10-12 - Page 111
Internal changes:
The queue handling logic has been slightly changed to allow run-
time structure type checking. The queue head is no longer marked
by a zero "kind" field, bit 7 (define Q_HEAD) of this field is
now set in a queue head instead. This should not interfere with
your programs unless you are traversing internal CTask queues
yourself (for example in a modified tsksnap module).
The structure and handling of timer/watch/hotkey elements has
changed. The link kind now identifies the kind of the element
itself, a new struckind field specifies the type of the structure
"strucp" points to. The element state is no longer needed, the
field was eliminated. To allow automatic removal on termination
of a secondary invocation, the elements are now chained.
Changes for CTask 2.0 to 2.1
Several bugs were fixed. The fixes include
- Killing the current task could crash the system if the task
had a dynamically allocated TCB and/or stack. A new "killer"
task was introduced to free those blocks after scheduling is
complete.
- Killing a task with a dynamically allocated stack would try
to free an invalid pointer.
- Timers and watches with the TKIND_WAKE attribute ignored the
repeat specification.
- Debugging CTask routines in single-step mode would sometimes
unexpectedly step into interrupt handlers, because the Trap
flag in the flag word on the interrupt stack would be
restored inside the stack switch routine, long before
exiting the handler.
- Timer/watch function calls were supposed to receive the user
parameter when called. Actually, the address of the
timer/watch control block is, and always was, passed. The
documentation has been corrected.
- The INT 15 wait/post handler could be overrun by fast disk
controllers, resulting in disk timeouts.
- When compiling for embedded systems, i.e. with GROUPS, DOS,
and IBM disabled, some statements would not compile, since
they referenced undefined field names.
- The prototype for c_schedule was missing.
Ctask Manual - Version 2.2 - 90-10-12 - Page 112
- The resource for INT 10 could be released prematurely if the
video routines used recursive calls. The request_cresource
is now used to catch this.
The routine get_priority now returns the initial, not the current
priority. With variable priority enabled, saving and restoring
the priority could lead to a tasks priority gradually increasing.
The keyboard handler now supports the extended INT 16 functions.
The t_read_key, t_wait_key, and t_keyhit routines use the
extended functions if available. The timer interrupt now checks
the BIOS keyboard buffer, and sets the key_avail flag to wake up
tasks waiting for the keyboard. This supports keyboard stuffers
putting characters directly into the buffer.
The TURBOC and MSC defines caused conflicts with other libraries.
They were renamed to TSK_TURBOC and TSK_MSC.
Resources, flags, counters, mailboxes, and pipes can now be
initialized statically, i.e. at compile time. This eliminates the
need to call the create_xxx routine. The only restriction is that
such events are not automatically linked into the name chain. A
new function, add_event_name, was created for this purpose.
Version 2.1 introduces keyboard hotkeys, which are similar to the
timer control blocks and the memory/port watches. Keyboard
hotkeys are trapped in the keyboard interrupt handler, and can
activate tasks or functions, or set flags, whenever a particular
key/shift-key combination is hit.
Timer control blocks, watchpoints, and keyboard hotkeys can now
specify counter decrementing in addition to counter incrementing.
A dec_counter function was added to explicitly decrement a
counter.
Support for EMS and 80x87 numeric coprocessors was added. The EMS
support saves and restores the EMS page map state on every task
switch to avoid problems with resident utilities, especially EMS-
based disk cachers. The 80x87 support saves and restores the
state of the coprocessor on a task switch.
The scheduler module was revisited, and interrupts are now
enabled most of the time. This should help to reduce interrupt
latency. In this context, checks for operations which are illegal
in interrupt handlers (task wait, kill current task) were added
to help diagnosing problems. Previous versions would usually
crash silently in those situations. The task switch save/restore
functions are now entered with interrupts enabled, and with DS
set to the data segment of the active task control block.
A replacement printf module was added. This assembler printf
replacement is faster than the standard C printf, and is not
model dependent. It can also output to the secondary screen in a
Ctask Manual - Version 2.2 - 90-10-12 - Page 113
dual-monitor system. This printf is used in the tsksnap module,
which is now model independent except for the snapshot() routine,
which uses two C runtime routines (flush and fileno). tsksnap
also was fixed to work with small model (some pointers had to be
explicitly forced to FAR). Some calls to the snapshot dump
routines now need different parameters.
A debugging mode provides real-time active task display, and on-
screen counters for the scheduler, plus counters for timer and
keyboard interrupts and internal interrupt stack usage. The
precompiled libraries do not provide debugging, you will have to
recompile with the DEBUG option in tskdeb.h changed.
The attributes for internal and external functions are now
#defined, and can thus easily be changed. This should ease
porting to other compilers that require different keywords. Also,
the attributes can now be defined for near code, allowing all
CTask functions to be compiled in small model if desired. Data
pointers still have to be far. All calls from Assembler routines
have been converted to macro calls, to allow customizing the
calling sequence. Several customization options have been added
to better support code-sharing and mixed model compilation and
assembly.
Secondary kernels now can share the CTask code with the primary
invocation. A jump-table linking external modules to installed
CTask code can be created, so that foreground programs
communicating with background CTask TSRs do not have to link in
the full CTask kernel code. A new function, "link_ctask", has
been added to check for code sharing installed in a primary
kernel. Several changes were necessary to allow access to
functions and variables local to a group from shared code. The
change also allows applications to call CTask functions from a
primary kernel without installing their own secondary kernel.
The mechanism for removing interrupt handlers upon group/kernel
termination was generalized. Previously, only two "remove"
routines (for the SIO and printer handlers) could be set. Version
2.1 introduces the "chain_removefunc" and "unchain_removefunc"
functions to allow multiple clean-up routines.
Some modules were split up, and IFDEF's added to TSKMAIN to allow
"secondary-only" programs. This change is intended for programs
that communicate with a background copy of CTask, and don't
intend to install the Kernel if that background copy does not
exist. The make-files create a "secondary" tskmain,
"stskmain.obj", which is not included in the library. Linking
with this stripped-down TSKMAIN saves 6-7k of program code.
A few other modules also were split up, and routines were
shuffled around, to avoid linking unreferenced routines. The
tsk.h and tsklocal.h files were amended to include the module
name defining the routine to make it easier to locate routines.
Ctask Manual - Version 2.2 - 90-10-12 - Page 114
Name pointers for tasks and structures may now be NULL.
Structures with NULL name pointers are not linked into the name
chain. This is useful for applications linking into CTask without
creating their own task group.
Name parameters are now specified using the #defined macro "TN".
This eliminates the need for explicit #if statements on each and
every name parameter. This change makes the code a lot more
readable.
Minor changes, mostly type-casts, were done in several modules to
eliminate compiler warnings.
The make-files for the support routines were changed to add the
model-letter to the filename for both object and library files.
This avoids confusion when you use more than one model. The
Turbo-Make files for the support routines and the sample
applications were updated to allow model modification from the
command line. The special ctsuph.tc make-file is no longer
needed.
Version 2.1 Interface Changes
The interface to the global kernel functions has not changed in
version 2.1. Note, however, that the v24_remove_func and
prt_remove_func entries have been replaced by the new remove
chaining, so this will require changes if you used one of those
entries to enter your own remove processing.
The global variable "ticks_per_sec" was moved to the global
variable block. Since this variable is no longer directly
accessible, the "ticks_per_sec" function was added.
If you were using task save/restore routines, or timer/watch
functions, please note the different handling of DS. DS is now
set to the data segment of the associated control block, which
normally, but not always, is equal to the segment created by the
file the block was defined in. See the "Advanced topics" chapter
in the manual for more information.
For timer/watch/hotkey functions, the parameter passed is a
pointer to the timer element, not the user parameter, as
erroneously specified in previous versions of the manual.
Interrupts are enabled on entry to all functions, including task
save/restore functions.
The "screensnap" function in the support module "tsksnap.c" was
changed. Previously, you had to specify the screen buffer address
and dimensions with the call to screensnap. The new version
requires initialising the screen driver with a call to
tsk_set_regen or tsk_set_dualdis before calling screensnap, which
now only accepts a screen length parameter. The function
"csnapshot" has been added to output to the screen using standard
Ctask Manual - Version 2.2 - 90-10-12 - Page 115
INT 10 calls. The "snapshot" function still takes a FILE *
parameter, but should preferably no longer be used to output the
snapshot dump to the screen, since it is significantly slower,
and requires DOS access. It should only be used to write the dump
to a file or a different device.
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.
Minor corrections were added 89-12-29 to fix a bug in
tsk_remove_int17 (file TSKINT17.ASM), and minor typos in the
documentation. The documentation was again fixed 90-01-02, and a
warning about TASM 1.0 incompatibilities was added to READ.ME.
Version 2.0 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.2 - 90-10-12 - Page 116
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.2 - 90-10-12 - Page 117
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.2 - 90-10-12 - Page 118
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.2 - 90-10-12 - Page 119
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
- tskdos.asm: Registers swapped in DOS version test
- tsksio.c: Incorrect loop variable in v24_sio_initialise
- 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, apart from bug fixes, 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
Ctask Manual - Version 2.2 - 90-10-12 - Page 120
Index
add_event_name 58
alloc_resource 96
AT_BIOS 19
BIOS 25
buffer 52
bufferptr 52
byte 36
byteptr 36
CALL_PASCAL 16
chain_removefunc 58
change_timer 68
CHECKING 20
check_buffer 87
check_counter 81
check_flag 79
check_mailbox 83
check_pipe 85
check_resource 77
check_wpipe 85
clear_counter 80
clear_flag 78
clear_flag_wait_set 79
CLOCK_MSEC 18
CLOCK_MSEC 64
code sharing 102
code sharing 103
CODE_SHARING 15
CODE_SHARING 102
console 25
counter 50
counterptr 50
create_buffer 86
create_counter 79
create_flag 77
create_hotkey_elem 72
create_hotkey_entry 74
create_mailbox 82
create_memory_watch 69
create_memory_watch_elem 68
create_pipe 83
create_port_watch 71
create_port_watch_elem 70
create_resource 75
create_task 62
create_ticker 65
create_timer 67
create_timer_elem 67
create_wpipe 83
CRITICAL 61
ctask_active 54
Ctask Manual - Version 2.2 - 90-10-12 - Index 1
ctask_resident 56
ctask_resident 102
curr_task 64
C_ENTER 61
C_LEAVE 61
c_read_buffer 87
c_read_pipe 84
c_read_wpipe 84
c_request_resource 77
c_schedule 60
c_wait_mail 83
c_write_buffer 87
c_write_pipe 85
c_write_wpipe 85
dec_counter 81
DEF_COUNTER 80
DEF_FLAG 78
DEF_MAILBOX 82
DEF_PIPE 84
DEF_RESOURCE 76
DEF_WPIPE 84
DEF_xxx 36
delete_buffer 86
delete_counter 80
delete_flag 78
delete_hotkey 74
delete_mailbox 82
delete_pipe 84
delete_resource 76
delete_ticker 65
delete_timer 68
delete_watch 72
delete_wpipe 84
disable_hotkey 74
disable_timer 68
disable_watch 72
DOS 19
DOS 25
DOS 103
dword 36
EMS 20
EMS 103
EMS_SAVE_SIZE 20
enable_hotkey 74
enable_timer 68
enable_watch 72
entry table 102
farptr 36
FAR_STACK 17
find_group_name 58
find_name 57
flag 50
flagptr 50
Ctask Manual - Version 2.2 - 90-10-12 - Index 2
flush_pipe 85
flush_wpipe 85
funcptr 36
funcptr_dword 36
funcptr_int 36
funcptr_void 36
F_CRIT 48
F_STTEMP 47
F_TEMP 47
F_USES_NDP 48
GEN_JTAB 102
get_priority 63
get_ticker 65
get_user_ptr 64
group control block 101
GROUPS 18
GROUPS 103
group_rec 49
Hotkey functions 105
HOTKEYS 20
HOTKEYS 103
IBM 19
IBM 103
IFL_DISK 55
IFL_INT15 56
IFL_INT8_DIR 55
IFL_NODOSVARS 56
IFL_NOEXITCHECK 56
IFL_PRINTER 56
IFL_VIDEO 55
inc_counter 81
installation flags 55
install_tasker 54
INT8_EARLY 19
INT8_LATE 19
intprocptr 36
kbflag 42
keyboard 25
kill_task 62
link_ctask 57
link_ctask 103
LNULL 35
LOAD_DS 16
LOAD_DS 102
LOCALS_FAR 16
mailbox 51
mailboxptr 51
msgptr 51
msg_header 51
NAMELENGTH 43
nameptr 43
namerec 43
NDP 20
Ctask Manual - Version 2.2 - 90-10-12 - Index 3
NDP 103
NEAR_CODE 16
NEAR_CODE 103
pipe 52
pipeptr 52
pipe_free 85
preempt_off 59
preempt_on 59
primary 101
primary kernel 101
PRI_INT8 19
PRI_STD 18
PRI_TIMER 18
prt_change_control 95
prt_complete 95
prt_flush 95
prt_install 94
prt_remove 94
prt_remove_all 95
prt_status 95
prt_wait_complete 95
prt_write 95
qelem_pri 37
queheadptr 38
queptr 37
queue 38
queue_head 38
read_buffer 87
read_pipe 84
read_wpipe 84
release_resource 76
remove function 105
remove routine 59
remove_tasker 56
request_cresource 77
request_resource 76
resource 51
resourceptr 51
restore function 44
restore function 105
ROM_CODE 17
save function 44
save function 105
schedule 60
sched_int 60
secondary 101
SECONDARY 102
secondary kernel 101
send_mail 82
set_counter 81
set_flag 78
set_funcs 64
set_priority 63
Ctask Manual - Version 2.2 - 90-10-12 - Index 4
set_task_flags 63
set_ticker 65
set_user_ptr 64
SINGLE_DATA 20
spawn 26
start_task 62
stop_task 63
stub table 102
ST_DELAYED 46
ST_ELIGIBLE 46
ST_KILLED 46
ST_RUNNING 46
ST_STOPPED 46
ST_WAITING 46
tcb 46
tcbptr 45
tcb_rec 45
TCMP_CHG 40
TCMP_EQ 40
TCMP_GE 40
TCMP_GES 40
TCMP_LE 40
TCMP_LES 40
TCMP_NE 40
TC_HUGE 16
TC_HUGE 102
TELEM_HOTKEY 40
telem_hotkey 42
TELEM_MEM 39
telem_memwatch 42
TELEM_PORT 39
telem_portwatch 42
telem_timeout 42
TELEM_TIMER 39
TFLAG_BUSY 41
TFLAG_ENQUEUE 41
TFLAG_REMOVE 41
TFLAG_REPEAT 41
TFLAG_TEMP 41
TFLAG_UNQUEUE 41
TFP_OFF 35
TFP_SEG 35
ticker 50
ticker 65
ticks_per_sec 64
tick_ptr 50
TIMEOUT 37
Timeout/watch functions 105
Timeouts 64
TKIND_COUNTDEC 39
TKIND_COUNTER 39
TKIND_FLAG 39
TKIND_PROC 39
Ctask Manual - Version 2.2 - 90-10-12 - Index 5
TKIND_TASK 39
TKIND_WAKE 39
tlink 42
tlinkptr 42
TMK_FP 35
TN 36
tsk_alloc 96
tsk_calloc 96
tsk_cli 61
tsk_disport 99
tsk_dis_int 60
tsk_dis_preempt 59
TSK_DYNAMIC 17
TSK_DYNAMIC 103
TSK_DYNLOAD 17
tsk_ena_int 61
tsk_ena_preempt 59
tsk_fprintf 97
tsk_free 96
tsk_inp 61
TSK_NAMED 18
TSK_NAMED 103
TSK_NAMEPAR 17
TSK_OFFSETOF 35
tsk_outp 61
tsk_printf 97
tsk_putc 97
tsk_puts 98
tsk_realloc 96
tsk_regen 99
tsk_regen_o 99
tsk_regen_s 99
tsk_rprintf 97
tsk_rputc 98
tsk_rputs 98
tsk_scheduler 60
tsk_setpos 99
tsk_set_attr 99
tsk_set_colour 98
tsk_set_currdis 98
tsk_set_dualdis 98
tsk_set_mono 98
tsk_set_regen 99
tsk_sprintf 97
tsk_sti 61
TSK_STRUCTOP 35
tsk_use_ndp 54
tsk_vfprintf 97
tsk_vprintf 97
tsk_vrprintf 97
tsk_vsprintf 97
TSR 26
TTIMEOUT 37
Ctask Manual - Version 2.2 - 90-10-12 - Index 6
TWAKE 37
TWATCH 37
TYP_BUFFER 43
TYP_COUNTER 43
TYP_FLAG 43
TYP_GROUP 43
TYP_HOTKEY 43
TYP_MAILBOX 43
TYP_PIPE 43
TYP_RESOURCE 43
TYP_TCB 43
TYP_TIMER 43
TYP_WATCH 43
TYP_WPIPE 43
t_delay 66
t_keyhit 88
t_read_key 88
t_wait_key 88
unchain_removefunc 59
user pointer 44
v24_change_baud 90
v24_change_dtr 90
v24_change_parity 91
v24_change_rts 90
v24_change_stopbits 91
v24_change_wordlength 91
v24_check 92
v24_complete 93
v24_define_port 88
v24_flush_receive 93
v24_flush_transmit 93
v24_install 89
v24_modem_status 93
v24_overrun 93
v24_protocol 92
v24_receive 92
v24_remove 90
v24_remove_all 90
v24_send 92
v24_wait_complete 93
v24_watch_modem 91
wait_counter_clear 80
wait_counter_set 80
wait_flag_clear 78
wait_flag_set 78
wait_hotkey 74
wait_mail 82
wait_memory 70
wait_pipe_empty 85
wait_port 72
wait_wpipe_empty 85
WAKE 37
wake_task 63
Ctask Manual - Version 2.2 - 90-10-12 - Index 7
WATCH 37
word 36
wordptr 36
wpipe 52
wpipeptr 52
wpipe_free 85
write_buffer 87
write_pipe 85
write_wpipe 85
yield 60
Ctask Manual - Version 2.2 - 90-10-12 - Index 8