home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-01-04 | 70.8 KB | 2,109 lines |
- Dear Programmer,
-
- thanks for using my Multi-Tasking Subsystem for Turbo Pascal 5.0.
- This product was designed to enable everyone to study programming in a
- multi-tasking environment, without having to spend a lot of money to
- purchase an operating system like OS/2 or XENIX (and of course the
- extension memory necessary to run it).
-
- The Turbo Pascal Multi-Tasking Subsystem is a very inexpensive and
- yet powerful tool that adds multi-tasking capabilities to your
- Turbo Pascal 5.0 programs.
- You will be able to study the programming of parallel processes and
- problems of interprocess communication using the programming
- environment you are accustomed to.
-
- Although the Multi-Tasking Subsystem initially was designed as a means
- of experimenting, it became very soon a powerful and easy to use
- product, which I hope you will like as much as I do.
-
- At first, I would like to outline the capabilities of the basic system:
-
- - up to 50 (increased on demand) independently executing
- tasks in a single program
- - 3 priority levels
- - code sharing
- - preemptive scheduler, using a dynamic scheduling algorithm
- - you may use DOS-functions in your tasks safely. A task is never
- interrupted within a DOS-function or a critical interrupt
- - size of timeslice and range of dynamic CPU-allocation programmable
- - message passing
- - semaphores
- - programmable timers
- - primitive event processing (up to 10 simultaneously active event
- requests; increased on demand)
- - executes on any PC, XT, AT, PS/2 and full compatible running DOS 2.x
- or 3.x
- - source code available to registered users
-
-
- Registered users will get an additional UNIT for FREE(!!) which is
- based upon the Multi-Tasking Subsystem and provides you with:
-
- - extended keycodes (Chacter, Scan-Code, Shift-Statusword)
- - manipulation of the keyboard buffer (clear, add keycodes at the
- end or in front of the actual buffer contents)
- - keyboard lock and unlock
- - execute a DOS-program as subtask
- - all you need to have tasks wait for a special hot-key that awakes
- them
- - all you need to have tasks pop up over the DOS-program and suspend
- the DOS-task while they are executing (another way of writing memory
- resident programs)
-
-
- Well, no product is perfect! If you would like to have a function the
- Multi-Tasking Subsystem does not include at present, contact me and
- perhaps you will find it in the next version. IF I decide to
- realize YOUR idea in a later version of the subsystem, you will
- receive a FREE-upgrade! I hope you will understand that I reserve
- the right to decide which functions will be added.
-
- In this connection, I would like to thank L. David Baldwin and
- TurboPower Software for their fabulous source-level debugger TDebug
- PLUS 4.01, that helped me very much during the development of my
- Multi-Tasking Subsystem.
-
-
- Let me stop here! Please take the time to read carefully through the
- documentation before you "dive" into the world of multi-tasking.
-
-
- Best regards,
-
- Christian Philipps
-
-
-
-
-
-
-
- Turbo 5.0 Multi-Tasking Subsystem
-
-
- User's Manual
-
-
-
-
-
-
- (c) Copyright 1988 by
- Christian Philipps, Moers
- all rights reserved
-
- Version 1.30 / November 1988
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Author: Christian Philipps
- Düsseldorfer Str. 316
- 4130 Moers 1
- West-Germany
-
-
-
-
- Trademarks Mentioned
-
- TurboPower Software is trademark of TurboPower Software, Scotts Valley
-
- Turbo Professional is trademark of Sunny Hill Software, used under
- license to TurboPower Software
-
- Turbo Pascal and TASM are registered trademarks of Borland International
-
- MS-DOS, OS/2 and MASM are trademarks of Microsoft Corporation
-
- A86 is trademark of Eric Isaacson Software, Bloomington
-
- UNIX is trademark of AT & T
-
- IBM PC, XT, AT, PS/2 and PC-DOS are trademarks of International
- Business Machines Corporation
-
-
-
- License Agreement
-
- The Turbo Pascal Multi-Tasking Subsystem is marketed through the
- ShareWare marketing concept.
-
- Therefore I grant you the right to reproduce, distribute and use
- copies of this ShareWare version of our Multi-Tasking Subsystem
- (including the on disk documentation), on the express condition that
- you do not receive any payment, commercial benefit, other
- consideration for such reproduction or distribution, or change this
- license agreement or the copyright notice.
-
- The Multi-Tasking Subsystem, the additional unit MTPOPUP and the
- documentation of both are copyright (c) 1988 by Christian Philipps,
- Düsseldorfer Str. 316, 4130 Moers 1, West-Germany.
-
- You have the right to evaluate this ShareWare version for a period of
- 30 days to find out whether it suits your needs. If you decide to
- continue using this software, I expect that you become a registered
- user by sending DM 50 or $35 (object code license) / DM 100 or $70
- (object code + source code license) to
-
- Christian Philipps
- Düsseldorfer Str. 316
-
- 4130 Moers 1
- West-Germany
-
- As soon as I receive your registration, I will send you your
- personal copy of the subsystem plus a FREE unit based upon the basic
- subsystem (see introductory letter).
- Furthermore you will get technical support through mail, e-mail or
- phone during 1 year from the day of receipt.
-
- Users purchasing an oject code license only may receive the source
- code of the latest version later at any point of time by sending
- another DM 50 or $35 to the adress above.
-
-
-
- Disclaimer
-
- I make no warranty of any kind, either express or implied, including
- but not limited to implied warranties of merchantability and fitness
- for a particular purpose, with respect to this software and the
- accompanying documentation.
-
- In no case, I will be liable for any damages (including damages for
- loss of business profits, business interruption, loss of business
- information, or other pecuniary loss) arising out of the use of or
- inability to use this software, even if I have been advised of the
- possibility of such damages.
-
-
-
- Getting Started
-
- Throughout this documentation I assume that you are familiar with
- Turbo Pascal 5.0 in general and with the UNIT concept.
- I also assume that you know how to use the advanced features of Turbo
- Pascal 5.0, for example type-casting of pointers. If necessary,
- please refer to your Turbo Pascal documentation for more information
- on any of the compiler's internal functions or the functions contained
- in the units Crt, Dos,...
-
-
- The Multi-Tasking Subsystem mainly consists of four units:
-
- a) CpMulti.TPU
-
- This is the basic Multi-Tasking Subsystem. All further units are
- based upon this piece of software.
-
- b) MTPOPUP.TPU (registered users only)
-
- MtPopUp is a unit, based upon the basic subsystem and providing
- full control over the system keyboard and supports one DOS-program
- to be run as a separate task. Tasks may be kept waiting for a
- certain key-combination to be pressed and somewhat resident
- programs can be written by executing another COMMAND.COM in the
- foreground. Please note that this unit is available only to
- registered users, who will receive it in return for their
- registration.
-
- c) QUEUE.TPU (Public-Domaine)
-
- This unit contains several functions, which help you to create,
- delete and manage forward-chained list-structures. The structure
- of a single list element is defined by your application and may be
- any data structure. Passing data between separate tasks, you will
- shortly find out that list structures, as provided by this unit,
- are convenient for implementing pipe-like data structures.
- Studying the routines contained in the accompanying source code for
- QUEUE, you will learn how to assure the consistency of shared data
- at any time during execution.
-
- d) CPMISC.TPU
-
- Miscellaneous service routines, used by QUEUE. The source code of
- this Unit is only partially made available.
-
-
- Let's take a look at the way to USE the Multi-Tasking Subsystem: The
- usage of this software is quite straightforward, provided you enable
- your compiler to find the necessary units (please refer to your
- compiler manual).
- You will incorporate the Multi-Tasking library in your programs by
- simply including CpMulti in the USES-statement. CpMulti must be the
- first unit of the subsystem preceded, however, by the basic units Crt
- (or alternatives) and Dos.
-
- Example: USES Crt, Dos, CpMulti, MtPopUp, Queue;
-
- The units CpMulti and MTPOPUP use Turbo's CRT-unit, so problems may
- arise, when using an alternative CRT-unit (such as the TurboProfes-
- sional unit TPCRT) in your program.
- For the special case that you own the fantastic TurboProfessional
- package of TurboPower Software, your diskette contains files with
- the extension .PRO, which I compiled using the TPCRT-unit. You only
- need to rename those files to the extension .TPU.
-
- If you wish to use another alternative Crt-unit that conflicts with
- the standard CRT-unit, you will have to order the source code and
- recompile the subsystem.
-
- After having set up your USES-statement properly, you will have to
- include the compiler switch S-, which prevents the compiler
- from generating object code that checks the stack boundaries. This
- is VERY important, because each task has its own private stack
- that is automatically reserved on the heap at the time of creation.
- Without the S- directive, you would get a stack overflow abort
- shortly after creating the first subtask.
-
- Whenever a task-switch is performed, the Multi-Tasking Subsystem
- checks the private stack of a subtask A task which uses up its
- stackspace (minus a certain safety margin) is aborted. The subsystem
- issues an error message and marks the task as "Crashed". The memory
- occupied by its private stack is not freed and may be examined using a
- debugger.
-
- Now let's have a look at the system startup. When your program comes
- to execution the initialisition code in CpMulti captures some
- interrupt vectors and sets up the task-table. It creates a
- null-process with lowest priority (a simple loop) that consumes
- CPU-time whenever no other process is currently computable. Finally
- the main program is started as task no. 2.
-
- The shutdown again is fully automatic. When your program terminates
- or a runtime error brings you to a halt, CpMulti's exit procedure
- gains control, disables multi-tasking and restores the interrupt
- vectors previously captured.
- Only in rare occasions, your system will completely crash. This
- could happen, when one of your tasks runs mad and overwrites
- valuable system data.
-
- You might start experimenting right now, BUT I recommend studying
- the rest of this documentation before. The following sections will
- give you a deeper understanding of the concept and the interior of the
- Multi-Tasking Subsystem.
- Multitasking is too complicated however, to go down to the last.
- Those of you, who would like do get a deeper insight into the design
- of multi-tasking operating systems should read the book "Operating
- Systems, Design and Implementation" by Andrew S. Tanenbaum, Prentice
- Hall or scan through the bookshelves of Prentice Hall or Adison
- Wesley. The book mentioned above helped me very much in designing the
- Multi-Tasking Subsystem and describes a UNIX-like operating system for
- personal computers in detail (MINIX + source code available from
- Prentice Hall).
-
-
- The Basic System (CpMulti)
-
- 1. Introduction
-
- At the moment, more and more programmers become interested in multi-
- tasking environments for personal computers. I suppose, anyone who
- intends to stay up to date, will have to deal with systems like OS/2
- or UNIX / XENIX /...
- Unfortunately, those systems are quite expensive and require lots of
- extension memory. Only few wealthy private programmers (have you
- ever seen one?) will be able to buy such an operating system already
- today.
-
- At the time I started developing the Multi-Tasking Subsystem, my
- situation had been as described above. Now I'm in the position to
- provide you with an inexpensive means to study the design and
- synchronisation of parallel processes. In addition to that, the
- Multi-Tasking Subsystem will help you to write programs more complex
- and much more sophisticated, especially in areas like data
- communication, where quite a lot of actions are to be taken nearly
- simultaneously.
-
-
- Concept
-
- Like UNIX or OS/2, the Multi-Tasking Subsystem was designed as a
- time-sharing system. It was not designed for real-time applications.
- A time-sharing system tries to divide the available computing power
- among all computable processes evenly. Therefore, a process that has
- used up its timeslice, is temporarily suspended and another
- computable process gains control (preemptive scheduling). This is
- accomplished by having a timer-interrupt-service-routine monitor the
- system activities about 18 times a second. This routine initiates a
- task-switch whenever the running task has used up its quantum.
- Although I could have used the AT-RTC (Real-Time Clock), which in fact
- would decrease the minimum possible timeslice, I have decided not to
- do so for several reasons:
-
- a) The task-switch is the most time consuming action, the kernel has
- to perform. A smaller timeslice, increases the number of possible
- task-switches, but increases the kernel overhead as well.
-
- b) The Multi-Tasking Subsystem could not run on PC or XT-computers,
- because those do not have a real-time clock.
-
- With a timer-interrupt occuring 18 times a second, the smallest
- possible timeslice is about 55,5 milliseconds. During the
- initialisation sequence the timslice is set to 110 milliseconds.
- If you choose a 55,5 millisecond timeslice the amount of computing
- power consumed by the mere task-switching process will be about 1,4
- percent (12 MHz AT-compatible).
-
-
- Dynamic Scheduling
-
- Normally each task is temporarily suspended whenever it has used up
- its timeslice. If this happes inside a DOS-function, however, the
- kernel may not safely interrupt the currently executing task. It has
- to wait, until it returns from the DOS-call.
- Provided this task has to read thousands of records from a data file
- and write them to another file, the kernel will be inside DOS
- again, when the kernel tries to suspend it at the following
- timer-tick. Again, a task switch is impossible! This procedure might
- happen again and again until the task has finished.
- As you see, under certain circumstances, a task may exceed its
- quantum at an amount that cannot be foreseen by the system-kernel.
-
- On the other hand, there might be a task that periodically, lets say
- one time a second, look at a particular value taken from a port to
- see, whether actions are to be taken. In this case, it will act upon
- the condition encountered, otherwise it will go to sleep and give up
- its timeslice. Such a task will use up its timeslice very seldom.
- If, hovewer, actions are to be taken, it might be suspended in the
- middle of its activities because it has used up its quantum. - Indeed
- that's not fair!
-
- What can we do to achieve some kind of compensation? OS/2 brought me
- to the idea to implement a somewhat dynamic scheduling that proceeds
- as follows: Whenever a task gives up its timeslice, the time not used
- is placed to this task's credit. If it has to execute longer at a
- later point of time, it will not be preempted until it has used up its
- current quantum plus its credit. On the other hand, a task that
- exceeds its quantum, is not rescheduled until its debit is "used up".
-
- This sounds quite good, but there is a catch to it though. If we
- would credit every single tick not used, the task mentioned above
- could possibly run for hours if it has to take some action after
- all.
- For this reason, the amount of ticks beeing placed to a tasks
- credit/debit, is limited. You may choose this limit freely and it
- defaults to +/- 10 timer-ticks that is slightly above half of a
- second.
-
-
- Priority
-
- The Multi-Tasking Subsystem supports three levels of priority:
- "Pri_Nice" which is the lowest, "Pri_User" which is standard and
- "Pri_Kernel" which is highest possible priority. Your Pascal
- main-program is started at the level "Pri_User" during initialization.
-
- Let's examine, how the different layers of priority are acted upon:
- Inside each layer, the tasks are scheduled in straight round-robin
- fashion. Whenever there are computable tasks at a higher level none
- of the tasks in a level below that will gain control. That means, the
- "idle loop" at level "Pri_Nice" will only be activated, if the
- layers "Pri_User" and "Pri_Kernel" are empty.
-
- The programmer has to take this concept into account when designing
- his application system. The overall system performance depends very
- much on the assignment of priorities. Any task can change its
- priority at runtime by means of a special system service.
-
- By the way: Throughout this documentation I use the terms "task" and
- "process" interchangably, although some experts will be picking up
- stones to throw at me at this very moment... I don't feel guilty
- doing so, because those experts seem not to have reached an agreement
- yet, about how to define these terms.
-
- Tasks that spend most of their lives waiting for events (waiting for
- incoming characters,...), should be given highest priority.
- Tasks that spend their lives busily working all the time, should be
- given standard pritority.
- By doing so, the busy ones will be interrupted whenever an event, a
- higher priorized task was waiting for occurs and will be able to work
- the rest of the time (which will be most of the time) undisturbed.
- The lowest priority level will mostly be occupied only by the idle
- loop.
-
-
- Another Catch To It
-
- DOS does not know about interrupt controlled disk I/O in the
- fashion of multi-tasking operating systems! There is a loop inside
- your ROM that waits for the disk-controller to issue a completion
- interrupt, but that is of no use for us, because a disk I/O
- operation must never be interrupted by a task-switch.
- Multi-tasking operating systems normally switch to another task,
- while the currently running process has to wait for a disk operation
- to complete. I didn't know how to achieve this without rewriting
- the low-level disk I/O-routines. So for now, we'll wait...
-
- As a consequence, a process that performs lots of disk I/O-operations
- and which is running at high priority, will possibly block the whole
- system.
- In a real multi-tasking operating system however, it is very useful
- running I/O-intensive processes at a high priority level for the
- reasons stated earlier in the context of mostly waiting tasks.
-
-
- Inter Process Communication (IPC)
-
- The Multi-Tasking Subsystem contains functions for handling
- semaphores and message-passing.
- Shared-memory does not require system support, because every task
- under DOS has access to any memory location in the whole system.
- The programmer will have to use semaphores, however, to avoid race
- conditions when accessing commonly used memory areas. The
- demonstration program PRO_CON gives examples of how to synchronize
- your tasks using semaphores.
-
-
- Programmable Timers
-
- Programmable timers are a very useful means to take care of timeout-
- conditions (for example in communications software).
- The Multi-Tasking Subsystem let you define any number (limited by
- the available heap) of timers with a resolution of 55,5
- milliseconds. If a timer expires, a byte located at the address given
- at the time of creation is incremented.
- The amount of CPU-time consumed by watching the timers currently
- active is negligible and independent of the number of timers defined.
-
-
- Event Handling
-
- Sometimes it can be useful to have a task waiting for the contents of
- a memory location to change. This is what I have chosen to call an
- event. Realizing a task that waits for a buffer filled by an
- interrupt routine, you can acchieve a very short response time at a
- minimum waste of CPU-time by defining an event that reactivates your
- task whenever the contents of the buffer's tail-pointer changes.
-
- Starting with release 1.30, the subsystem kernel puts a task,
- awakened by an event occuring, at the head of the task-queue
- corresponding to its run level. Thus it is put in the position of
- beeing able to react on the event very quickly.
-
-
-
- 3. How Is It Done
-
- Most of the subsystem is written in Turbo Pascal 5.0. Only a small
- part of the kernel (about 2 KB) is written in assembly language. If
- you purchased a source code license, you will find two versions of
- the assembly language module on your product diskette. The first one
- assembles using Microsofts Macro Assembler V4.0 or higher or TASM 1.0,
- the second one is for programmers using the well known ShareWare
- assembler A86 V3.19 or higher, written by Eric Isaacson. Both parts
- together form a TPU (Turbo Pascal Unit) that contains the whole
- subsystem and may be USEd in your programs.
-
-
- The Task Table
-
- The central element of the Multi-Tasking Subsystem is called "task
- table". For reasons of performance, this table was defined
- statically in the data segment. It can hold a maximum of 50 tasks at
- the moment, but can easily be expanded (if you own the source code).
-
- Every task in the system occupies one entry in the task table. This
- entry holds all pieces of information that describe the task's actual
- state of execution.
-
- At any point of time each task is in one of the following possible
- states:
-
- Running..: This task is currently executed.
-
- Ready....: This task is computable, but another task currently is in
- control of the CPU.
-
- Sleeping.: This task has suspended itself for a certain number of
- timer-ticks.
-
- Crashed..: The subsystem has detected a stack overflow for this task.
-
- Waiting..: This task is currently waiting for an event or a
- resource (a semaphore for example).
-
- Sending..: This task is waiting for the receiver of a message to
- do a receive system call.
-
- Receiving: This task is waiting for another task to send it a message.
-
- Suspended: This task was temporarily disabled by another task in the
- system and needs action from another task to be put into
- the ready-state again.
-
- If a slot in the task-table currently is not occupied by any task its
- state-marker will contain the value "Available".
-
- The following state diagram summarizes all possible task-states and
- the state-transitions possible among these states. A state-transition
- may only take place in the direction indicated by the arrow symbols.
-
-
-
-
-
-
- ┌───────────┐ 13 ┌───────────┐
- ┌──────>│ Running ├───────┬──────>│ Crashed │
- │ └─────┬─────┘ │ └───────────┘
- │ │ │
- 2│ 1│ │
- │ │ └────────────┐
- │ │ │
- ┌─────┴─────┐ │ 4 ┌───────────┐ │
- │ Ready │<──────┴─────┬─┤ Waiting │<─┐3 │
- └───────────┘ │ ├───────────┤ │ │
- 6├─┤ Sleeping │<─┤5 │
- │ ├───────────┤ │7 │
- 8├─┤ Sending │<─┼───┘
- │ ├───────────┤ │
- 10├─┤ Receiving │<─┤9
- │ ├───────────┤ │
- 12└─┤ Suspended │<─┘11
- └───────────┘
-
-
-
-
- State Diagram, Multi-Tasking Subsystem Version 1.10
-
-
- Task Management
-
- Internally, the tasks are managed using queues. Dependent on its
- state of execution, a task may be located in
-
- a) the scheduler queue of its priority-level ("Running" or "Ready"),
- b) the sleep-queue ("Sleeping") or
- c) the sender-queue of the receiver-task, if the latter one is not
- ready to receive the message and the sender indicates that it
- wants to wait until the receiver gets ready ("Sending").
-
- Tasks at the states "Suspended", "Receiving" or "Crashed" are
- not located in any queue.
- A task that is "Waiting" on an event or a resource may be chained to
- the event-table or be located in a semaphore's task-queue.
-
- Whenever a task is created, a task-table entry is allocated to
- this task. The slot number of its task-table entry becomes the
- task-id (task-number or process-number), which some system-calls
- are to be passed as a parameter.
- In addition to that, a private stack for the task is allocated and
- set up.
-
- The task creation can be completed sucessfully only if a) there is
- an empty slot in the task-table and b) the private stack can be
- allocated on the heap.
-
-
-
- Code-Sharing
-
- The Turbo Pascal compiler generates object code that is mostly
- reentrant. Such code may be executed in code-sharing.
-
- The term "Code-sharing" can have different meanings. On the one
- hand there may be a number of subroutines which are used by more
- than one task in the system. Thus more than one task may be
- executing a certain subroutine at the "same" point of time, i. e.
- the instruction pointers of those tasks point to a location inside
- this subroutine at the same point of time.
- Provided the code is reentrant, this will never lead to any
- problems. You will have to take care of situations which lead to
- more than one task modify global data at the same point of time, of
- course, but the type of code-sharing described above will never lead to
- a crash or the modification of local variables.
-
- "Code-sharing" may also describe a situation, in which the same part
- of code (here: the object code of a procedure - the task) is activated
- more than once as a separate process. Each of these tasks is given
- its own set of registers, its private stack and its local variables.
- The latter are actually allocated on the private stack. Provided the
- code is reentrant and provided that you, the programmer, take care of
- race conditions, there may be an "unlimited" number of separate tasks
- executing the same object code simultaneously.
-
- I have already tried both kinds of code-sharing and did not encounter
- any problems so far.
-
-
- Programmable Timers
-
- Lets now have a closer look at the timers. A timer, which in fact is
- a memory location containing a counter beeing decremented at any
- timer-tick, needs to be examined whenever a clock-interrupt occurs.
- If the kernel realizes that the timer has expired, some action will
- be to be taken.
- As you see, a small extra amount of computing power is necessary to
- countdown timers, act upon their expiration and finally remove them
- from the system. Some types of application, communication programs
- for example require extensive use of timers. Therefore one should
- think about an inexpensive implementation of timers to avoid loss of
- performance.
- The implementation of timers in the Multi-Tasking Subsystem makes the
- kernel overhead constant and independant from the number of timers
- presently active. This is achieved by a special queueing method,
- taken from the timer implementation of MINIX, a UNIX-like operating
- system for the IBM PC. As stated earlier, MINIX has been developed by
- Andrew S. Tanenbaum and is currently distributed by Prentice Hall. I
- recommend MINIX for everybody, who would like to go deeper into
- operating system design, it's fantastic!
-
- Return to the timers: The timer-queue consists of a forward-chained
- list of timer-structures. Only the first element of this list is
- examined at every timer-tick. If the first timer expires, it will be
- removed from the queue after having taken the appropriate action to
- signal its expiration to the application. The second element now
- becomes the head of the queue, and so on.
- When a new timer is inserted into the timer-queue, its counter will be
- adjusted to reflect the number of ticks remaining after all previous
- elements of the timer-queue have expired.
-
- Example:
- ┌─────┬─┐ ┌─────┬─┐ ┌─────┬─┐
- Timer-Queue ───>│ 3 │─┼───>│ 5 │─┼───>│ 1 │─┼─┤
- └─────┴─┘ └─────┴─┘ └─────┴─┘
- Time to wait: 3 Ticks 8 Ticks 9 Ticks
-
-
- Event Handling
-
- You might think of lots of events that could be handled by an
- application.
- Currently only the change of contents with respect to a single
- memory location is supported. Practically speaking, your application
- may wait (without consuming CPU) until the contents of a particular
- memory location changes. This memory location is checked whenever a
- task-switch occurs.
-
- What to do with it? Well, I came up with this type of event as I
- designed a special application. I had to realize a task that emptied
- a ring-buffer which was filled by an interrupt routine. Why not use a
- semaphore, you might ask! - Too slow! There was just enough time to
- insert the characters received into the ring-buffer. On the other
- hand the ring-buffer had to be emptied as quickly as possible, again
- for the reasons of speed.
- A high-priorized subtask permanently looping, keeping an eagle eye on
- the ring-buffer is far from beeing a solution - it simply blocks the
- whole system. OK, lets try to have the subtask sleep for a while,
- take a look at the buffer and sleep again if no characters have
- arrived. Well, in the meantime a buffer-overflow has occured...
-
- I suppose, you now understand why the event has been born. Defining
- an event that monitored the buffer's tail-pointer, I kept my
- high-priority subtask waiting for the buffer to be filled without
- blocking the whole system. Awakend very shortly after a change of the
- tail-pointer signalizes incoming characters, it busily processed the
- buffer contents and went to sleep again. - Works fine!
-
-
- Inter Process Communication (IPC)
-
- As mentioned earlier, the Multi-Tasking Subsystem provides for
- various methods to exchange data between separate tasks and to
- synchronize processes executing simultaneously.
- I do not intend to go into detail about the underlying concepts,
- because there are lots of books available in the market, which discuss
- these topics much better than I could do here in this document. Again
- I recommend the study of "Operating Systems Design and Implementation"
- written by Andrew S. Tanenbaum.
-
-
- A. Message Passing
-
- The system-calls supporting message passing are quite
- straightforward.
- A task that wants to send a message to another task in the system
- primarily has to know the task-id of the process it would like to
- send to. Then it passes its message-buffer and the receiver-id to
- the appropriate system-call. An additional parameter tells the
- kernel, whether the sender wants to wait if the receiver is not
- ready to receive the message or whether the send-request should be
- rejected.
-
- A task is ready to receive, whenever it executes a receive-system-
- call. It may specify the task-id of the process it wants to receive
- from or pass a don't-care code to the system-call, which causes the
- kernel to pass through any message sent to this task.
- Again, the task may wait until a message arrives or have the system
- reject a receive system-call whenever no message is available.
-
-
- B. Semaphores
-
- There has been written a lot about this topic in the past. Let's have
- a look at the internal represetation only at this point.
-
- A semaphore as defined by the Multi-Tasking Subsystem, consists of a
- two-byte signal count, which is set to 1 at the time of creation,
- and a task-queue to which the processes waiting for this semaphore
- are appended.
- The busy-state is indicated by a signal-count of zero; any non-zero
- value indicates a free-state. Whenever a wait system-call is executed
- and the corresponding semaphore is currently busy, the requesting
- task is suspended until another process releases this semaphore
- through execution of a signal system-call.
- Whenever a signal system-call is executed and the task-queue of the
- corresponding semaphore is not empty, the first task of this queue
- is made computable, otherwise the signal-count is incremented.
-
- Despite the internal structure, tasks refer to semaphores through
- untyped pointers. The task creating a semaphore receives this
- pointer on return from the system-call and uses it as a handle
- furtheron. The actual number of semaphores in the system is
- delimited by the amount of free heap space only.
-
-
- Critical Sections
-
- You may use the standard functions/procedures supplied with your
- compiler freely inside your tasks. BUT - procedures like WriteLn are
- not designed to work in multi-tasking environments, that means, they do
- not protect their critical sections from beeing entered more than once
- at a time.
- An example will help to understand what I try to explain: Lets think
- of two equaly priorized tasks which spend their lives WriteLn-ing
- strings to the terminal screen. The process of bringing a bunch of
- characters to the screen is much more complicated than the simple
- WriteLn-statement makes believe. Therefore it is likely that
- sometimes both tasks will be preempted while in the middle of
- performing the screen-write. As a consequence the screen output
- becomes clobbered.
- Imagine the effects of non-synchronized execution of GetMem and
- FreeMem...
-
- Another situation in which you have to block a certain section of code
- is a routine, that modifies global data or performs some closely
- connected actions that form an undivisible unity (atomic action).
- Let me give you an example: Provided you got two tasks executing
- simultaneously, which at some point of execution output a character to
- the screen at a certain position. This is done by executing a GotoXY,
- followed by a Write. If the first task was interrupted after having
- executed the GotoXY but before having output the character, the second
- task might move the cursor to another location on the screen, which
- indeed wouldn't lead to the results desired.
-
- There are two basically different approaches to solve this problem.
- First you could force exclusive CPU-access during the execution of
- your critical section. This solution is only applicable for very
- small portions of code, a GotoXY/Write-sequence for example, because
- a CPU-bind blocks any scheduler function including the sleep-queue
- and timer management.
- In most cases a protection scheme using semaphores is preferable.
- You can find an example for this kind of process synchronisation in
- the example program PRO_CON.PAS, routines RBuffGet and RBuffPut. A
- section of code protected by a semaphore, may only be entered by one
- task at a time. Any task trying to enter the critcal section whilst
- another process is currently inside is suspended until the latter one
- has left the protected area.
-
-
-
- Global Type- and Data-Definitions
-
-
- Global Type-Definitions
-
- Priority = (Pri_Nice, Pri_User, Pri_Kernel);
-
- This type-definition is used to describe the priority-level at
- which a task is to be executed.
-
-
- WaitFlagType = (Wait, NoWait);
-
- Passed as a parameter to the system-calls concerned with
- message-passing, this type indicates whether a process intends to
- wait for a) a message to be received or b) a message available to
- be received.
-
-
- TaskReturn = (Task_Crashed, Task_NotFound, Task_NoMsg,
- Task_NotReady, Task_OK, Task_Invalid);
-
- The system-calls concerned with message-passing return one of these
- codes to the calling process.
-
- Task_Crashed...: A task was addressed, which has been disabled
- following a stack-overflow condition
- Task_NotFound..: The task-table entry indexed by the task-id
- passed to the executed system-function is
- out of range or currently empty
- Task_NoMsg.....: There is no message available (Receive with NoWait)
- Task_NotReady..: The receiver is not waiting for a message (Send
- with NoWait)
- Task_OK........: Fine, that's it
- Task_Invalid...: An invalid task-id was passed to the
- system-function
-
-
- TaskStatus = (Running, Ready, Sleeping, Available, Crashed,
- Waiting, Sending, Receiving, Suspended);
-
- Return value returned by GetTaskState.
-
-
- SemReturn = (Sem_NoSpace, Sem_NotFree, Sem_OK, Sem_Invalid);
-
- Some system-calls concerned with semaphore-handling return a value
- of type SemReturn.
-
- Sem_NoSpace..: There is no heap space available
- Sem_NotFree..: You have tried to delete a semaphore whose task-queue
- is not empty
- Sem_OK.......: Everything's allright
- Sem_Invalid..: You passed an invalid semaphore-handle (NIL)
-
-
- TaskNoType = Integer;
-
- Higher level representation of the task-id. I decided to redefine
- the type integer to be able to change this definition in a later
- version without having to think about the program code.
-
-
- BPtr = ^Boolean;
-
- Definition required by GetTimBusy.
-
-
-
- Global Constants
-
- Task_NoSpace
-
- If task creation fails, because there is not enough free dynamic
- memory available to allocate a private stack, CreateTask will return
- this value instead of the task-id.
-
-
- Task_NoSlot
-
- If task creation fails, because the maximum number of simultaneously
- active tasks is reached, CreateTask will return this value instead
- of the task-id.
-
-
- StateText
-
- StateText is an array indexed by a value of type TaskStatus, which
- contains a textual representation of every possible task state.
-
-
- AnyTask
-
- Don't-care-value, passed to the receive system-function whenever a
- process doesn't care from whom it is going to receive a message.
-
-
- Global Variables
-
- InInt28 (Boolean)
-
- Whenever COMMAND.COM is waiting for input, it permanently issues
- an INT 28H (DOS multi-tasking interrupt). MtPopUp for example
- catches this interrupt and sets InInt28 to TRUE whilst inside the
- interrupt-handler. This informs the kernel know that a task-switch
- may safely performed although the DOS critical-flag is set.
-
-
-
- Internal Type- and Data-Definitions
-
- Internal Type-Definitions
-
- CSDataType = RECORD
- TimBusy : Boolean;
- Old08 : Pointer;
- ISRStat : Byte;
- END;
-
- CSDataType describes a small status-area located in the code
- segment of the assembly language module.
- "TimBusy", the timer-busy flag, is set to a non-zero value
- whenever the interrupt-service routine handling timer-tick
- interrupts is currently executing. This prevents reentrance, not
- allowed in this case.
- If you block the CPU by executing BindCPU, this flag is also set
- to simulate a timer-busy condition.
- "Old08" contains the interrupt-vector pointing to the original
- interrupt-handling procedure.
- There are various interrupt-handlers that must not be disturbed by a
- task-switch (for example the disk-I/O vector 13H). The byte
- addressed by "ISRStat" is used to store a number of interrupt-busy
- flags, one for each interrupt that may not be interrupted. By
- entering the corresponding interrupt-handling procedure, the flag is
- set to 1 and reset on return. At task-switch may only be performed,
- if both, the critical-flags and the ISRStat, contain zero.
-
-
- TaskPtrType = ^TaskDescType;
-
- Pointer to a task-table slot.
-
-
- SchedRecType = RECORD
- First : TaskPtrType;
- Last : TaskPtrType;
- END;
-
- The anchor of a task-queue. Such task-queues are represented
- internally as forward-chained list structures. The scheduler-queues
- for example are task-queues that contain all tasks in the system
- that are computable or running at the moment. The first element of
- the queue is addressed using the pointer "First", the tail of the
- queue may be found by following the pointer "Last". This enables
- the kernel to quickly append new elements to the end of the queue.
-
-
- TaskDescType = RECORD
- NextTask : TaskPtrType;
- TaskNo : TaskNoType;
- SaveArea : SaveAreaType;
- Status : TaskStatus;
- RunLevel : Priority;
- Stack : PointerType;
- StackSize : Word;
- CPU : LongInt;
- TimeSlice : Integer;
- DelayCount: LongInt;
- Senders : SchedRecType;
- RecFrom : TaskNoType;
- RecBuff : Pointer;
- END;
-
- Here you see the structure of a so-called task-descriptor, which
- makes up one slot of the task-table.
- "NextTask" is used to chain the tasks in a queue together. The
- queue is terminated by a NIL value contained in the NextTask field.
- The task-id is stored in the field named "TaskNo". This value is
- identical to the slot-number of the corresponding task-descriptor.
- Quite a number of system-calls need this value to index into the
- task-table when manipulating the contents of a particular task-
- descriptor.
- As you already know, every task possesses a private stack. The
- actual top of stack as represented by the registers SS and SP, is
- stored in the structure named "SaveArea". The actual size and
- location of this stack are stored in fields named "Stack" and
- "StackSize". The kernel checks for a stack-overflow condition
- whenever a task-switch is performed.
- "Status" contains the acutal state of a task. If this field
- contains the value "Available", the task-table slot is empty and
- may be assigned to a newly created task.
- The field indicating a task's priority is named "RunLevel". The
- contents of this field is used to select the appropriate
- scheduler-queue.
- "TimeSlice" is used to keep the number of timer-ticks already used
- up during the current timeslice. This value might become negative
- due to dynamic scheduling.
- The last field concerned with time-sharing is named "DelayCount".
- Whenever a task goes to sleep for a while, this field is filled
- with the number of ticks the task intends to stay asleep. It is
- decremented on every timer-tick until the value zero is reached.
- Than the task is re-scheduled.
- The remaining fields in the task-descriptor are concerned with
- message-passing. "Senders" is the anchor of a task-queue
- containing the tasks wishing to send to this process.
- "RecFrom" is used to indicate which task a process wants to
- receive from on the one hand and to hold the task-id of the sender
- after having successfully received a message on the other hand.
- The latter function is important when receiving messages using the
- don't care code "AnyTask". The receiver will not know in advance
- from whom he will receive a message. He may find it out by
- questioning the contents of "RecFrom".
- "RecBuff" points to the message-buffer.
-
-
- Semaphore = RECORD
- Signals : Word;
- WaitQueue : SchedRecType;
- END;
-
- Here we have the internal representation of a semaphore. The
- signal-count is stored in "Signals" and the tasks kept waiting for
- the semaphore to become free, are chained to the anchor WaitQueue.
-
-
- EventType = RECORD
- MemLoc : Pointer;
- OrgValue : Byte;
- Task : TaskNoType;
- END;
-
- We have already heard about events and what they are for.
- Here is how they are stored internally.
- "MemLoc" points to the memory-location to monitor.
- "OrgValue" contains the original contents of the byte at MemLoc^.
- "Task" finally is the task-id of the requesting task. It is used
- to index into the task-table when re-scheduling the task.
- Right before re-appending the task to its scheduler-queue, the
- kernel checks whether it is still alive!
-
-
- TimerType = RECORD
- MemLoc : Pointer;
- Ticks : Word;
- Task : TaskNoType;
- Next : TimerPtr;
- END;
-
- This is how timers are stored. Whenever a timer is created, the
- necessary memory is allocated on the heap. Than the timer is
- inserted into the TimerQueue. The single timers contained in this
- queue are chained through their "Next" pointers. A NIL-pointer
- terminates the timer-queue.
- "Ticks" is used to store the number of timer-ticks to wait until
- the timer expires.
- "MemLoc" points to a memory location whose content is incremented
- as soon as the timer expires. Before the kernel actually increments
- the byte at "MemLoc", it checks whether the task is still alive.
- If not, no action is taken for not to destroy a memory location
- that may already belong to another task's private address-space.
- "Task" again holds the task-id of the requesting process, which is
- used to index into the task-table.
-
-
- PointerType = RECORD CASE Integer OF
- 1: (P : Pointer);
- 2: (POfs : Word;
- PSeg : Word);
- END;
-
- Split up a pointer in its segment and offset components.
-
-
- Internal Constants
-
- TicksPerSecond = 18;
-
- This constant is used to describe the number of timer-ticks that
- make up a second. You could easily increase the number of
- task-switches by using the RTC of an AT-computer for example. If
- after the change this constant reflects the new number of
- interrupts per second, any program using Seconds() to calculate
- its sleeping time, will not have to be adapted.
-
-
- MaxEvents = 10;
-
- "MaxEvents" determines the maximum slots in the event-table. It is
- recommended to use the smallest possible value to keep the amount
- of time needed to search this table as small as possible.
-
-
- Quantum : Byte = 2;
- MaxDynQuantum : Byte = 10;
-
- Simply default values that may be changed by means of the
- TimeSlice function.
-
-
- MaxTasks = 50;
-
- This value determines the maximum number of slots in the
- task-table.
-
-
- SafetyMargin = 20;
-
- Whenever the kernel checks for stack-overflow conditions, it
- assumes a margin of "SafetyMargin"-bytes from the highest possible
- stack-address to be "untouchable" and issues a stack-overflow
- message if any byte in this area is used by the corresponding task.
-
-
- Internal Variables
-
- TaksTable : ARRAY[1..MaxTasks] OF TaskDescType;
-
- The root of all evil - the task-table.
-
-
- CurrentTask : TaskPtrType;
-
- This pointer is used to hold the address of the task-descriptor
- belonging to the currently running task. It may only be
- manipulated by the scheduler.
-
-
- TaskQueues ARRAY[Pri_Nice..Pri_Kernel] OF SchedRecType;
-
- These are the anchors of the various scheduler-queues.
-
-
- SleepQueue : SchedRecType;
-
- Here your tasks may sleep for a while....
-
-
- EventTable : ARRAY[1..MaxEvents] OF EventType;
-
- Whenever an EventWait is issued, a slot of this table is allocated
- for the requested event. The number of table entries available
- reflects the maximum possible number of simultaneously active
- events.
-
-
- ActiveEvents : Byte;
-
- To be able to skip event-checking, the kernel counts the number of
- currently active events. Whenever this field contains zero, no
- check for events is performed.
-
-
- TimerQueue : TimerPtr;
-
- This is the head of the timer-queue. If the pointer contains NIL,
- no timers are currently active.
-
-
- InDosFlag : PointerType;
- DosCritical : PointerType;
-
- DOS uses two memory-locations to store flags that contain a
- non-zero value whenever DOS is not in a consistent state. The
- pointers named above are used to hold the addresses of those
- memory-locations.
-
-
- CSDataPtr : ^CSDataType;
-
- This pointer holds the address of a small status area located in the
- code-segment of the assembly language module.
-
-
- Reference Section (alphabetically sorted)
-
- BindCpu
-
-
- Declaration
-
- PROCEDURE BindCPU;
-
- Purpose
-
- BindCPU is used to bind the CPU to the currently running process
- temporarily. No task-switch will be performed an no other
- scheduler activities take place until the CPU is released by a
- ReleaseCPU call.
-
-
- Example
-
- ...
- BindCPU; {gain CPU exclusivly}
- GotoXY(1,10); {atomic action}
- Write('*');
- ReleaseCPU;
- ...
-
- Remarks
-
- BindCPU blocks the whole kernel! While the CPU is bound, no
- event-checks, no timer-countdown and no sleep-queue handling are
- performed.
- You should only use BindCPU to protect VERY small portions of
- code. In most cases, the use of semaphores is recommended
-
- See Also
-
- ReleaseCPU
-
-
-
- CancelTimer / CreateSem
-
-
- Declaration
-
- FUNCTION CancelTimer(MemPtr:Pointer):BOOLEAN;
-
- Purpose
-
- If there exists a timer that will cause the memory location at the
- address "MemPtr" to become incremented, it will be removed from the
- system. CancelTimer returns TRUE, if a timer could be found,
- otherwise FALSE is returned.
-
- Example
-
- VAR Timeout : Boolean;
- Ok : Boolean;
- ...
- Timeout := False; {initialize}
- IF QueueTimer(@Timeout,Seconds(1)) {create Timer}
- THEN BEGIN
- REPEAT {wait}
- ... {some action}
- UNTIL Timeout OR Ok;
- IF CancelTimer(@Timeout) THEN; {Kill Timer}
- ... {some more action}
- END;
-
- See Also
-
- QueueTimer
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- FUNCTION CreateSem(VAR SemPtr:Pointer):SemReturn;
-
- Purpose
-
- CreateSem allocates a semaphore on the heap and returns a pointer
- that functions as a handle in further system-calls referring to
- this semaphore. The handle is placed into the variable SemPtr.
- The return value of this system-function indicates whether the
- action could be completed successfully.
-
- Example
-
- VAR Semaphore : Pointer; {Handle}
- ...
- IF CreateSem(Semaphore) <> Sem_Ok
- THEN Error;
-
- See Also
-
- Typedefinition SemReturn, RemoveSem
- CreateTask
-
-
- Declaration
-
- FUNCTION CreateTask(TaskPointer:Pointer;Level:Priority;
- BytesStack:Word):TaskNoType;
-
- Purpose
-
- This function is used to create a subtask.
- The address of the procedure to be activated as an independently
- running task is to be passed in "TaskPointer".
- "Level" describes the desired priority level.
- Finally, the parameter "BytesStack" contains the desired size of
- the task's private stack in bytes.
-
- CreateTask returns the task-id or a negative value indicating an
- error-condition.
-
- Example
-
- PROCEDURE SubTask;
- {
- I am a task that beeps every second
- }
- BEGIN {SubTask}
- REPEAT { Body ─┐ }
- Sleep(Seconds(1)); { │ }
- Sound(1000); { ACTION!! │ }
- Delay(20); { : │ }
- NoSound; { │ }
- UNTIL False; { Body ─┘ }
- END; {SubTask}
- ...
- ...
- BEGIN {Main}
- IF CreateTask(@SubTask,Pri_User,300) < 0
- THEN Error;
- ...
- END. {Main}
-
- Remarks
-
- With respects to programming, a task is realized as a Pascal
- procedure without parameters, whose body has to be an infinite
- loop. You must NEVER leave the body of a task except by executing
- a Terminate system-call.
- A task is allowed to have local variable definitions, you will
- have to take care however, that the private stack is properly
- dimensioned to be able to hold the local data. You will have to
- add the amount of memory needed to hold the local variables to
- the bytes stackspace the task needs at runtime.
- YOU are responsible for passing a valid stack size!!!! This
- parameter is not checked!!
-
- See Also
-
- Typedefinitions TaskNoType and Priority, Global Constants,
- Terminate
-
-
- DoShutDown
-
-
-
- Declaration
-
- PROCEDURE DoShutDown;
-
- Purpose
-
- Shutdown the Multi-Tasking Subsystem. All interrupt vectors
- captured are restored to their original state.
-
- Remarks
-
- This procedure is called internally through the exit-procedure of
- the subsystem-unit.
-
- DumpTaskTable
-
-
- Declaration
-
- PROCEDURE DumpTaskTable;
-
- Purpose
-
- Display the actual state of all active tasks in the system.
-
- Example
-
- Nr. Status Priorität Slice Delay Stack free CPU
- 1 Ready Pri_Nice 2 0 205 18
- 2 Waiting Pri_User 3 0 -1 1
- 3 Running Pri_Kernel -1 0 221 1
- 4 Waiting Pri_User 2 0 321 5
-
- Remarks
-
- The current content of your display are overwritten. In case you
- wish to realize a status-window, you should replace DumpTaskTable
- with a routine of your own.
-
- See Also
-
- Global Constants StateText, GetTaskState
- EventWait / GetPID
-
-
- Declaration
-
- FUNCTION EventWait(MemPtr:Pointer):Boolean;
-
- Purpose
-
- EventWait causes your task to be suspended until the contents of
- the memory-location pointed at by "MemPtr" changes.
- If no empty event-table entry can be found, EventWait returns
- FALSE.
-
- Example
-
- VAR HeadPointer : Word; {Bufferpointer }
- TailPointer : Word; { " }
- ...
- IF NOT EventWait(@TailPointer) {wait for a change}
- THEN Error {of the pointer}
- ELSE DoJob;
- ...
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- FUNCTION GetPID:TaskNoType;
-
- Purpose
-
- By calling GetPID, a task can find out its task-id.
-
- See Also
-
- Typedefinition TaskNoType
- GetTaskState / GetTimBusy
-
-
- Declaration
-
- FUNCTION GetTaskState(Task:TaskNoType):TaskStatus;
-
- Purpose
-
- GetTaskState returns the current status of the task whose task-id
- was passed to the function.
-
- Example
-
- This task displays its own state on the screen:
-
- Writeln(StateText[GetTaskState(GetPID)]);
-
- See Also
-
- Typedefinitions TaskStatus and TaskNoType, Constant StateText
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- FUNCTION GetTimBusy:BPtr;
-
- Purpose
-
- GetTimBusy returns the address of the timer-busy flag which
- contains a non-zero value whenever the interrupt-handling
- procedure for the INT 8 is active.
- Sometimes an external interrupt-handler needs to know, whether
- the scheduler is currently busy.
- No system-call must be executed whilst the busy-flag is set.
-
- See Also
-
- Typedefinition BPtr
-
- QueueTimer / ReadySuspended
-
-
- Declaration
-
- FUNCTION QueueTimer(MemPtr:Pointer; Counter:Word):BOOLEAN;
-
- Purpose
-
- Creates a timer that expires after "Counter" ticks and increments
- the contents of the memory-location pointed at by "MemPtr".
- If there is not enough heap space available to allocate a timer,
- QueueTimer will return FALSE, otherwise TRUE is returned.
-
- Example
-
- VAR Timeout : Boolean; {Timeout marker}
- ...
- Timeout := False; {initialize }
- IF NOT QueueTimer(@Timeout,Seconds(1)) {Timeout after 1 sec}
- THEN Error;
- ...
- IF CancelTimer(@Timeout) {Remove Timer }
- THEN ; {if still existent}
- ...
-
- See Also
-
- CancelTimer
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- FUNCTION ReadySuspended(Task:TaskNoType):BOOLEAN;
-
- Purpose
-
- ReadySuspended is used to re-enqueue a task into its
- scheduler-queue wich has been suspended using the Suspend
- system-function.
- If the action could be completed successfully, the value TRUE is
- returned. A return value of FALSE indicates that the task addressed
- was not currently suspended.
-
- See Also
-
- Suspend
- Receive
-
-
- Declaration
-
- FUNCTION Receive(FromTask:TaskNoType; MsgBuff:Pointer;
- WaitFlag:WaitFlagType):TaskReturn;
-
- Purpose
-
- By executing a Receive system-call, a task can receive a message
- from another task in the system.
- The first parameter, "FromTask", indicates from which task the
- caller would like to receive. If "FromTask" contains "AnyTask",
- any message is passed through, otherwise only messages sent by
- the process whose id matches "FromTask" are received.
- "MsgBuff" points to the start of a message buffer into which the
- received message is copied. The kernel does not check whether the
- message received fits into the message buffer - it simply
- copies!
- If the caller passes a "WaitFlag" of value "Wait", it will be
- suspended until a message arrives. Otherwise the kernel
- immediately returns control to the caller if no message is
- currently available.
-
- Example
-
- VAR Puffer : String; {Message buffer}
- ...
- IF Receive(AnyTask,@Puffer,Wait) <> Task_Ok
- THEN Error
- ELSE Writeln(Puffer);
- ...
-
- Remarks
-
- Using the don't care task-id "AnyTask", you could realize a task
- that spends its life waiting for messages from its environment.
- If you had to monitor some sensors, for example, you could have a
- separate task for every sensor to be monitored. Whenever some
- action has to be taken, the corresponding monitor-task sends a
- message to a central workhorse that performs the necessary
- adjustments.
-
- See Also
-
- Constant AnyTask, Typedefinition TaskReturn, Send, ReceivedFrom
- ReceivedFrom / ReleaseCPU
-
-
- Declaration
-
- FUNCTION ReceivedFrom:TaskNoType;
-
- Purpose
-
- By calling ReceivedFrom, a task can find out, who was the sender
- of the last message received.
-
- See Also
-
- Typedefinition TaskNoType, Constant AnyTask, Receive
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- PROCEDURE ReleaseCPU;
-
- Purpose
-
- Provided you have bound the CPU by executing a BindCPU, you will
- have to reenable scheduling through execution of a ReleaseCPU.
-
- Example
-
- ...
- BindCPU; {disable scheduler}
- GotoXY(1,10); {atomic action}
- Write('*');
- ReleaseCPU; {...set them free...}
- ...
-
- See Also
-
- BindCPU;
- RemoveSem / Sched
-
-
- Declaration
-
- FUNCTION RemoveSem(VAR SemPtr:Pointer):SemReturn;
-
- Purpose
-
- RemoveSem physically removes a semaphore. The formerly occupied
- heap space is returned to the memory pool.
- "SemPtr" must be a valid semaphore-handle as returned by the
- CreateSem system-call.
- RemoveSem returns a value of type SemReturn that indicates
- success or failure.
-
- Example
-
- VAR Semaphore : Pointer; {Handle}
- ...
- IF CreateSem(Semaphore) <> Sem_Ok
- THEN Error;
- ...
- IF RemoveSem(Semaphore) <> Sem_Ok
- THEN Error;
- ...
-
- Remarks
-
- The system mostly is not able to prove whether a valid handle is
- supplied. It is your job to take care of this.
- You cannot remove a semaphore, whose task-queue currently is not
- empty.
-
- See Also
-
- Typedefinition SemReturn, CreateSem
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- PROCEDURE Sched;
-
- Purpose
-
- Initiate a task-switch, give up the current timeslice.
- "Sched" is used internally by many system-functions. If you want
- to have your task give up its timeslice, you should better use
- Sleep(1).
-
- Seconds / SemClear
-
-
- Declaration
-
- FUNCTION Seconds(Sec:Word):LongInt;
-
- Purpose
-
- "Seconds" returns the number of timer-ticks that make up a second.
- It is recommended that you use this function whenever you need to
- specify a special amount of time. Use of Seconds makes your
- application independent of changes to the task-switch rate.
-
- Example
-
- IF QueueTimer(@Timeout,Seconds(1) SHR 1) { timout after }
- THEN; { half a second }
-
- See Also
-
- QueueTimer, CancelTimer
-
-
- ────────────────────────────────────────────────────────────────────
-
-
-
- Declaration
-
- PROCEDURE SemClear(SemPtr:Pointer);
-
- Purpose
-
- Reset a semaphore's signal-count to zero.
-
- Example
-
- VAR Semaphore : Pointer; {Handle}
- ...
- IF CreateSem(Semaphore) <> Sem_Ok
- THEN Error
- ELSE SemClear(Semaphore);
-
- Remarks
-
- Internally SemClear is translated into SemSet(Semaphore,0).
-
- See Also
-
- SemSignal, SemWait, SemClearWait, SemSet
- SemClearWait
-
-
- Declaration
-
- PROCEDURE SemClearWait(SemPtr:Pointer);
-
- Purpose
-
- Reset a semaphore's signal-count to zero and wait until another
- task initiates a SemSignal system-call for this semaphore.
- In contrast to SemWait, SemClearWait ALWAYS leads to the calling
- task beeing suspended.
-
- Example
-
- VAR Ready : Pointer; {Semaphore }
- ...
- PROCEDURE SubTask;
- {
- I am a task that needs to save the values of some global
- variables during initialisation. The main program, however,
- changes these variables during the process of its execution.
- Therefore, the main program has to wait until I indicate that I
- have transferred the values I need to my local data area.
- }
- BEGIN {SubTask}
- ... {initialize }
- SemSignal(Ready); {O.K. I'm ready }
- REPEAT
- ... {Body }
- UNTIL False;
- END; {SubTask}
- ...
- ...
- BEGIN {Main}
- ...
- IF CreateSem(Ready) <> Sem_Ok {Create a semaphore}
- THEN Error;
- IF CreateTask(@SubTask,Pri_User,300) < 0 {Create a task }
- THEN Error
- ELSE SemClearWait(Ready); {wait for O.K. }
- ...
- END. {Main}
-
- See Also
-
- SemClear, SemSet, SemSignal, SemWait
-
- SemCut / SemGetSignals
-
-
- Declaration
-
- FUNCTION SemCut(SemPtr:Pointer;Task:TaskNoType):BOOLEAN;
-
- Purpose
-
- If the task whose id is passed as "Task" currently is waiting in
- "SemPtr"'s task-queue, it is removed from this queue.
- SemCut returns TRUE, if the action could be completed successfully.
-
- Remarks
-
- SemCut is a somewhat brutal function! It forces an innatural
- action and was added for a special application purpose in MtPopUp.
- PLEASE do use it with care!! - Better keep away from it.
-
- See Also
-
- SemPaste
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- FUNCTION SemGetSignals(SemPtr:Pointer):Word;
-
- Purpose
-
- Returns the signal-count of the semaphore "SemPtr".
-
- Example
-
- FUNCTION SemBusy(S:Pointer):BOOLEAN;
- {
- This function checks whether a semaphore is currently busy.
- }
- BEGIN {SemBusy}
- SemBusy := (SemGetSignals(S) = 0); {Signal-Count = 0}
- END; {SemBusy}
-
- See Also
-
- SemSignal, SemWait, SemClear, SemClearWait, SemSet
- SemPaste / SemSet
-
-
- Declaration
-
- PROCEDURE SemPaste(SemPtr:Pointer; Task:TaskNoType);
-
- Purpose
-
- SemPaste unconditionally appends "Task" to the task-queue of the
- semaphore referred to by "SemPtr". No validity checks are performed!
-
- Remarks
-
-
- SemCut is a somewhat brutal function! It forces an innatural
- action and was added for a special application purpose in MtPopUp.
- PLEASE do use it with care!! - Better keep away from it.
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- PROCEDURE SemSet(SemPtr:Pointer; Count:Word);
-
- Purpose
-
- SemSet sets the signal-count of a semaphore to a definite value.
- This action does not have any influence on tasks that might be
- waiting in the task-queue belonging to this semaphore.
-
- Example
-
- CONST Elements = 100;
- VAR Buffer : ARRAY[1..Elements] OF Byte; {Buffer}
- Full : Pointer; {No. of used slots}
- Empty : Pointer; {No. of empty slots}
- ...
- BEGIN {Main}
- ...
- IF CreateSem(Full) = Sem_Ok
- THEN SemClear(Full); {no slot used}
- IF CreateSem(Empty) = Sem_Ok
- THEN SemSet(Empty,Elements); {all slots free}
- ...
- END. {Main}
-
- See Also
-
- SemSignal, SemWait, SemClear, SemClearWait
- SemSignal / SemSoWaiting
-
-
- Declaration
-
- PROCEDURE SemSignal(SemPtr:Pointer);
-
- Purpose
-
- If the task-queue of the semaphore referred to by "SemPtr" is
- currently empty, the signal-count will be incremented. Otherwise
- the first task waiting is re-scheduled and the signal-count remains
- unchanged.
-
- Example
-
- VAR Critical: Pointer;
- ...
- IF SemCreate(Critical) <> Sem_Ok
- THEN Error;
- ...
- SemWait(Critical);
- ...
- {critical section}
- ...
- SemSignal(Critical);
- ...
-
- See Also
-
- SemWait, SemClearWait, SemClear, SemSet
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- FUNCTION SemSoWaiting(SemPtr:Pointer):BOOLEAN;
-
- Purpose
-
- SemSoWaiting returns TRUE, if there are tasks waiting for
- "SemPtr" to become available.
-
- See Also
-
- SemWait, SemSignal, SemClear, SemClearWait, SemSet, SemGetSignals
- SemWait / Send
-
-
- Declaration
-
- PROCEDURE SemWait(SemPtr:Pointer);
-
- Purpose
-
- If a semaphore's signal count currently is greater than zero, it
- will be decremented. Otherwise the caller is suspended and
- appended to the task-queue of this semaphore.
- It remains suspended until another task issues a SemSignal for
- the semaphore it is waiting for.
-
- Example
-
- VAR Critical: Pointer;
- ...
- IF SemCreate(Critical) <> Sem_Ok
- THEN Error;
- ...
- SemWait(Critical);
- ...
- {critical section}
- ...
- SemSignal(Critical);
- ...
-
- See Also
-
- SemSignal, SemClearWait, SemClear, SemSet
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- FUNCTION Send(ToTask:TaskNoType; Msg:Pointer; MsgSize:WORD;
- WaitFlag:WaitFlagType):TaskReturn;
-
- Purpose
-
- The system-function "Send" lets a task send a message to another
- process in the system.
- The parameter "ToTask" contains the task-id of the receiver. This
- must be the task-id of a currently active task. You must not use
- the constant "AnyTask" in this context.
- "Msg" points to the start of the message-buffer whose size is
- passed in "MsgSize".
- Finally a wait-flag has to be supplied, that indicates whether
- the sender wishes to wait until the receiver is willing to
- receive the message. If you set "WaitFlag" to "NoWait", the
- kernel will return control to your task immedeately if the
- receiver is not waiting for a message.
- Send / Sleep
-
-
- Example
-
- VAR Puffer : String;
- ...
- IF Receive(AnyTask,@Puffer,Wait) <> Task_Ok
- THEN Error
- ELSE Writeln(Puffer);
- ...
-
- Remarks
-
- The message is physically copied to the message buffer of the
- receiving process. This is not the best solution from the point
- of view of performance, but is provided for the highest possible
- independence of sender and receiver. They could theoretically be
- located on different machines in a networking environment. If
- you better like a pointer to a message to be passed, make your
- pointer the message.
-
- See Also
-
- Typedefinition TaskReturn, Receive
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- PROCEDURE Sleep(Ticks:LongInt);
-
- Purpose
-
- "Sleep" lets your task suspend itself for a certain number of
- timer-ticks.
-
- Example
-
- Writeln('I''m going to sleep for 3 seconds!');
- Sleep(Seconds(3));
- Writeln('Hi, here I am again!');
-
- See Also
-
- Seconds
- SetPri
-
- Declaration
-
- PROCEDURE SetPri(Pri:Priority);
-
- Purpose
-
- "SetPri" lets a Task change its priority at runtime.
-
- Remarks
-
- The new priority will come into effect at the end of
- its current quantum. Therefore the task will remain in
- control of the CPU, although it might have lowered its
- priority.
-
- Suspend / Terminate
-
-
- Declaration
-
- FUNCTION Suspend(Task:TaskNoType):BOOLEAN;
-
- Purpose
-
- Brutally suspend a task which is currently computable.
- "Task" contains the id of the task to be suspended.
- A task that has been suspended by a Suspend system-call can ONLY
- be reactivated by a ReadySuspended system-call.
- If the task addressed was not computable, Suspend would return
- FALSE to the caller. Otherwise TRUE is returned.
-
- See Also
-
- Typedefinition TaskNoType, ReadySuspended
-
-
- ────────────────────────────────────────────────────────────────────
-
-
- Declaration
-
- PROCEDURE Terminate;
-
- Purpose
-
- A task may terminate itself by executing a "Terminate"-system-call.
- The task-descriptor is marked available and the private stack is
- returned to the memory pool.
-
- See Also
-
- CreateTask
-
- TimeSlice
-
-
- Declaration
-
- PROCEDURE TimeSlice(Slice, DynQ:Byte);
-
- Purpose
-
- The width of a timeslice and the +/- maximum number of ticks to
- credit/debit during dynamic scheduling, may be changed by issuing
- a "TimeSlice"-system-call.
- "Slice" describes the size of a timeslice in timer-ticks;
- "DynQ" contains the number of ticks to credit/debit during
- dynamic scheduling.
- "Slice" defaults to 2; "DynQ" defaults to 10.
-
-
- History
-
- Version 1.30 / November 1988
-
- - Task queues are now realized as doubled chained lists; this
- increases speed when removing list elements
- - Tasks beeing awakened by an event or returning from a message
- passing operation, are now put to the front of their run level
- task queue. Thus they will be selected by the scheduler earlier.
- - A bug in the delta computation of QueueTimer has been corrected
- - The scheduler stack is no more located in the data segment, but
- has been moved to the heap
- - The formerly local data type "PointerType" has been made public
- - The assembly language module has become slightly optimized
- - A new procedure "SetPri" has been added