

<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.amigaos.net/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Gianfranco+Gignina</id>
	<title>AmigaOS Documentation Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.amigaos.net/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Gianfranco+Gignina"/>
	<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/wiki/Special:Contributions/Gianfranco_Gignina"/>
	<updated>2026-05-09T18:08:21Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=AmigaDOS_Introduction&amp;diff=3617</id>
		<title>AmigaDOS Introduction</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=AmigaDOS_Introduction&amp;diff=3617"/>
		<updated>2012-08-02T04:58:43Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* About AmigaDOS Processes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:DOS]]{{WIP}}&lt;br /&gt;
= About AmigaDOS =&lt;br /&gt;
&lt;br /&gt;
AmigaDOS is the name given to a subsystem group of modules which give to the AmigaOS capability to handle I/O with memorize units like hard disks, SSD etc...&lt;br /&gt;
AmigaDOS extends AmigaOS capabilities with:&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;dos.library&#039;&#039;&#039;: the AmigaDOS core library, it gives many utility functions to manage I/O of files, directories, processes, etc...;&lt;br /&gt;
*&#039;&#039;&#039;Filesystem&#039;&#039;&#039;: an AmigaDOS (dos.library) process which handles how system organizes data on phisical memory units like hard disks, SSD, etc...;&lt;br /&gt;
*&#039;&#039;&#039;CLI&#039;&#039;&#039;: Command Line Interface for user with which he can interact with AmigaDOS processes without use of GUI paradigm;&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
AmigaDOS is built on Exec basis and extends parts of it. One important example of this enhancement is the concept of Process.&lt;br /&gt;
As it&#039;s known, Exec gives you capabilities to handle processes called Tasks, however a Task doesn&#039;t have tools to handle I/O with memorize units. AmigaDOS gives you a new kind of extended Task which is called Process. An AmigaDOS process is a task with capabilities to handle files and so to interact with them.&lt;br /&gt;
&lt;br /&gt;
To handle Processes, AmigaDOS gives you many utility function to create and destroy them, and many other ones to handle files, directory etc... These utility functions are grouped into dos.library.&lt;br /&gt;
&lt;br /&gt;
A particular AmigaDOS process is the Filesystem: it handles how files and directories will be memorized on disks, and it receives all requests you will do with dos.library functions concerning files and directories. Filesystem will reply you using dos.library functions returns, so for you this communication is totally transparent.&lt;br /&gt;
&lt;br /&gt;
AmigaDOS provides a process that you can use, called &#039;&#039;&#039;Command Line&lt;br /&gt;
Interface&#039;&#039;&#039; or &#039;&#039;&#039;Shell&#039;&#039;&#039;. There may be several Shell processes running&lt;br /&gt;
simultaneously, numbered from 1 onwards. The Shell processes read &#039;&#039;&#039;commands&#039;&#039;&#039;&lt;br /&gt;
and then execute them. To make additional Shell processes, you use the&lt;br /&gt;
NEWSHELL or RUN commands. To remove a Shell process use the ENDSHELL&lt;br /&gt;
command. (You can find a full description of these commands in&lt;br /&gt;
Chapter 2).&lt;br /&gt;
&lt;br /&gt;
= Console Handling =&lt;br /&gt;
&lt;br /&gt;
You can direct information that you enter at the terminal to a Command&lt;br /&gt;
Line Interface (Shell) that tells AmigaDOS to load a program, or you can&lt;br /&gt;
direct the information to a program running under that Shell. In either&lt;br /&gt;
case, &#039;&#039;&#039;console&#039;&#039;&#039; (or &#039;&#039;&#039;terminal&#039;&#039;&#039;) &#039;&#039;&#039;handler&#039;&#039;&#039; processes input and output. This&lt;br /&gt;
handler also performs local line editing and certain other functions. You&lt;br /&gt;
can type ahead as many as 512 characters - the maximum line length.&lt;br /&gt;
&lt;br /&gt;
To correct mistakes, you press the BACKSPACE key. This erases the last&lt;br /&gt;
character you typed. To rub out an entire line, hold down the Ctrl key&lt;br /&gt;
while you press X. This &#039;&#039;&#039;control combination&#039;&#039;&#039; is referred to from this point&lt;br /&gt;
on in the manual as Ctrl-X. You may also use the left and right cursor&lt;br /&gt;
keys to move within the command line to insert or remove characters if you&lt;br /&gt;
make a mistake.&lt;br /&gt;
&lt;br /&gt;
You can also search for the most recent occurrence of a specific command&lt;br /&gt;
by typing the command line, or the beginning of it, then pressing Shift-Up&lt;br /&gt;
(or Ctrl-R). For instance, if you type DIR and press Shift-Up, you will be&lt;br /&gt;
returned to the last command to perform a DIR of any directory. Pressing&lt;br /&gt;
Shift-Down moves you to the bottom of the history buffer, leaving the&lt;br /&gt;
cursor on a blank line.&lt;br /&gt;
&lt;br /&gt;
In addition to command line editing, the Shell also provides command&lt;br /&gt;
history, which allows you to recall previously-entered command lines, edit&lt;br /&gt;
them, and re-enter them. This is useful when you want to repeat a command&lt;br /&gt;
or enter several very similar commands.&lt;br /&gt;
&lt;br /&gt;
The shell uses a 2K command line buffer to retain command lines. The&lt;br /&gt;
exact number of lines varies depending on [the] lengths of the lines&lt;br /&gt;
actually stored. When the buffer fills up, the oldest lines are lost. You&lt;br /&gt;
access lines in the buffer through the up and down cursor keys:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| up cursor || Moves backwards in the history buffer (earlier lines).&lt;br /&gt;
|-&lt;br /&gt;
| down cursor || Moves forwards in the history buffer (later lines).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If you type anything, AmigaDOS waits until you have finished typing before&lt;br /&gt;
displaying any other output. Because AmigaDOS waits for you to finish, you&lt;br /&gt;
can type ahead without your input and output becoming intermixed. AmigaDOS&lt;br /&gt;
recognizes that you have finished a line when you press the RETURN key.&lt;br /&gt;
You can also tell AmigaDOS that you have finished with a line by&lt;br /&gt;
cancelling it. To cancel a line, you can either press Ctrl-X or press&lt;br /&gt;
BACKSPACE until all the characters on the line have been erased. Once&lt;br /&gt;
AmigaDOS is satisfied that you have finished, it starts to display the&lt;br /&gt;
output that it was holding back. If you wish to stop the output so that&lt;br /&gt;
you can read it, simply type any character (pressing the space bar is the&lt;br /&gt;
easiest), and the output stops. To restart output, press BACKSPACE,&lt;br /&gt;
Ctrl-X, or RETURN. Pressing RETURN causes AmigaDOS to try to execute the&lt;br /&gt;
command line typed after the current program exits.&lt;br /&gt;
&lt;br /&gt;
AmigaDOS recognizes Ctrl-\\ as an end-of-file indicator. In certain&lt;br /&gt;
circumstances, you use this combination to terminate an input file. (For a&lt;br /&gt;
circumstance when you would use Ctrl-\\, see &amp;quot;Understanding Device Names&amp;quot;,&lt;br /&gt;
below).&lt;br /&gt;
&lt;br /&gt;
If you find that strange characters appear on the screen when you type&lt;br /&gt;
anything on the keyboard, you have probably pressed Ctrl-O by mistake.&lt;br /&gt;
AmigaDOS recognizes this control combination as an instruction to the&lt;br /&gt;
console device (CON:) to display the alternative character set. To undo&lt;br /&gt;
this condition, you press Ctrl-N. Any further characters should then&lt;br /&gt;
appear as normal. You could press Esc-C to reset the console. This clears&lt;br /&gt;
the screen and displays normal text.&lt;br /&gt;
&lt;br /&gt;
The table below summarizes the editing capabilities of the Amiga&#039;s Shell&lt;br /&gt;
interface:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Key&lt;br /&gt;
! Shell Editing Commands&lt;br /&gt;
|-&lt;br /&gt;
| left cursor || Moves cursor one character to the left.&lt;br /&gt;
|-&lt;br /&gt;
| right cursor || Moves cursor one character to the right.&lt;br /&gt;
|-&lt;br /&gt;
| Shift-left cursor || Moves cursor to the beginning of the line.&lt;br /&gt;
|-&lt;br /&gt;
| Shift-right cursor || Moves cursor to the end of the line.&lt;br /&gt;
|-&lt;br /&gt;
| Backspace || Deletes the character to the left of the cursor.&lt;br /&gt;
|-&lt;br /&gt;
| Del || Deletes the character highlighted by the cursor.&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-H || Deletes the last character (same as BACKSPACE).&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-M || Processes the command line (same as RETURN).&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-J || Adds a line feed.&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-W || Deletes the word to the left of the cursor&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-X || Deletes the current line.&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-K || Deletes everything from the cursor forwards to the end of the line.&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-Y || Replaces the characters deleted with Ctrl-K.&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-U || Deletes everything from the cursor backwards to the start of the line.&lt;br /&gt;
|-&lt;br /&gt;
| Space bar (or any printable character) || Suspends output (stops scrolling).&lt;br /&gt;
|-&lt;br /&gt;
| Backspace || Resumes output (continues scrolling).&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-C || Sends a BREAK command to the current process (halts the process).&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-D || Sends a BREAK command to the current script (halts the script).&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-S || Suspends output.&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-Q || Resumes output if it was suspended with Ctrl-S.&lt;br /&gt;
|-&lt;br /&gt;
| Ctrl-\ || Closes the Shell window.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Finally, AmigaDOS recognizes all commands and argument typed in either&lt;br /&gt;
upper or lower case. AmigaDOS displays a filename with the characters in&lt;br /&gt;
the case used when it was created, but finds the file no matter what&lt;br /&gt;
combination of cases you use to specify the filename.&lt;br /&gt;
&lt;br /&gt;
= Using the Filing System =&lt;br /&gt;
&lt;br /&gt;
This section describes the AmigaDOS filing system. In particular, it&lt;br /&gt;
explains how to name, organize, and recall your files.&lt;br /&gt;
&lt;br /&gt;
A file is the smallest named object used by AmigaDOS. The simplest&lt;br /&gt;
identification of a file is by its filename, discussed below in &amp;quot;Naming&lt;br /&gt;
Files&amp;quot;. However, it may be necessary to identify a file more fully. Such&lt;br /&gt;
an identification may include the device or volume name, and/or directory&lt;br /&gt;
name(s) as well as the filename. These will be discussed in [the]&lt;br /&gt;
following sections.&lt;br /&gt;
&lt;br /&gt;
== Naming Files ==&lt;br /&gt;
&lt;br /&gt;
AmigaDOS holds information on disks in a number of files, named so that&lt;br /&gt;
you can identify and recall them. The filing system allows filenames to&lt;br /&gt;
have up to 30 characters, where the characters may be any printing&lt;br /&gt;
character except slash (/) and colon (:). This means that you can include&lt;br /&gt;
space ( ), equals (=), plus (+) and double quote (&amp;quot;), all special&lt;br /&gt;
characters recognized by the CLI, within a filename. However, if you use&lt;br /&gt;
these special characters, you must enclose the entire filename within&lt;br /&gt;
double quotes. To introduce a double quote character within a filename,&lt;br /&gt;
you must type an asterisk (*) immediately before that character. In&lt;br /&gt;
addition, to introduce an asterisk, you must type another asterisk. This&lt;br /&gt;
means that a file named:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
A*B = C&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
should be typed as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;quot;A**B = C*&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the CLI to accept it.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Note:&#039;&#039; This use of the asterisk is in contrast to many other operating systems where it is used as a universal &#039;&#039;wild card&#039;&#039;. An asterisk by itself in AmigaDOS represents the keyboard and the current window. For example,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
COPY filename TO *&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
copies the filename to the screen. On the Amiga, the universal wild card is #?.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When spaces are used within a file, directory, or device name, quotes&lt;br /&gt;
are required when accessing the name. For example,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
COPY &amp;quot;df0:My file&amp;quot; TO ram:&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avoid spaces before or after filenames because they may cause confusion.&lt;br /&gt;
&lt;br /&gt;
== Using Directories ==&lt;br /&gt;
&lt;br /&gt;
The filing system also allows the use of directories as a way to group files together into logical units. For example, you may use two different&lt;br /&gt;
directories to separate program source from program documentation, or to keep files belonging to one person distinct from those belonging to another.&lt;br /&gt;
&lt;br /&gt;
Each file on a disk must belong to a directory. An empty disk contains one directory, called the root directory. If you create a file on an empty disk, then that file belongs to the root directory. However, directories may themselves contain other directories. Each directory may therefore contain files, or yet more directories, or a mixture of both. Any filename is unique only within the directory it belongs to, so that the file &amp;quot;fred&amp;quot; in the directory &amp;quot;bill&amp;quot; is a completely different file from the one called &amp;quot;fred&amp;quot; in the directory &amp;quot;mary&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This filing structure means that two people sharing a disk do not have to worry about accidentally overwriting files created by someone else, as&lt;br /&gt;
long as they always create files in their own directories.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=WARNING|text=When you create a file with a filename that already exists, AmigaDOS deletes the previous contents of that file. No message to that effect appears on the screen. You can also use this directory structure to organize information on the disk, keeping different sorts of files in different directories.}}&lt;br /&gt;
&lt;br /&gt;
An example might help to clarify this. Consider a disk that contains two directories, called &amp;quot;bill&amp;quot; and &amp;quot;mary&amp;quot;. The directory &amp;quot;bill&amp;quot; contains two files, called &amp;quot;text&amp;quot; and &amp;quot;letter&amp;quot;. The directory &amp;quot;mary&amp;quot; contains a file called &amp;quot;data&amp;quot; and two directories called &amp;quot;letter&amp;quot; and &amp;quot;invoice&amp;quot;. These subdirectories each contain a file called &amp;quot;jun18&amp;quot;. [This figure] represents this structure as follows:&lt;br /&gt;
&lt;br /&gt;
                        ROOT&lt;br /&gt;
                          |&lt;br /&gt;
               +----------+----------+&lt;br /&gt;
               |                     |&lt;br /&gt;
             BILL                  MARY&lt;br /&gt;
               |                     |&lt;br /&gt;
          +----+----+           +--------+--------+&lt;br /&gt;
          |         |           |        |        |&lt;br /&gt;
        TEXT     LETTER       DATA    LETTER   INVOICE&lt;br /&gt;
                                         |        |&lt;br /&gt;
                                       JUN18    JUN18&lt;br /&gt;
&lt;br /&gt;
{{Note|text=The directory &amp;quot;bill&amp;quot; has a file called &amp;quot;letter&amp;quot;, while the directory &amp;quot;mary&amp;quot; contains a directory called &amp;quot;letter&amp;quot;. However, there is no confusion because both...are in different directories. There is no limit to the depth that you can &amp;quot;nest&amp;quot; directories.}}&lt;br /&gt;
&lt;br /&gt;
To specify a file fully, you must include the directory that owns it, the directory owning that directory, and so on. To specify a file, you&lt;br /&gt;
give the names of all the directories on the path to the desired file. To separate each directory name from the next directory or filename, you type a following slash (/). Thus, the full specification of the data files on the disk shown...above is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bill/text&lt;br /&gt;
bill/letter&lt;br /&gt;
mary/data&lt;br /&gt;
mary/letter/jun18&lt;br /&gt;
mary/invoice/jun18&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Setting the Current Directory ==&lt;br /&gt;
&lt;br /&gt;
A full file description can get extremely cumbersome to type, so the&lt;br /&gt;
filing system maintains the idea of a current directory. The filing&lt;br /&gt;
system searches for files in this current directory. To specify the&lt;br /&gt;
current directory, you use the CD (&#039;&#039;&#039;C&#039;&#039;&#039;urrent &#039;&#039;&#039;D&#039;&#039;&#039;irectory) command. If you have&lt;br /&gt;
set &amp;quot;mary&amp;quot; as your current directory, then the following names would be&lt;br /&gt;
sufficient to specify the files in that directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
data&lt;br /&gt;
letter/jun18&lt;br /&gt;
invoice/jun18&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can set any directory as the current directory. To specify any files&lt;br /&gt;
within that directory, simply type the name of the file. To specify files&lt;br /&gt;
within subdirectories, you need to type the names of the directories on&lt;br /&gt;
the path from the current directory specified.&lt;br /&gt;
&lt;br /&gt;
All the files on the disk are still available even though you&#039;ve set up&lt;br /&gt;
a current directory. To instruct AmigaDOS to search through the&lt;br /&gt;
directories from the root (top level) directory of a volume (disk or&lt;br /&gt;
partition), you type a colon (:) at the beginning of the file description.&lt;br /&gt;
Thus, when your file description [sic] has the current directory set to&lt;br /&gt;
&amp;quot;mary&amp;quot;, you can also obtain the file &amp;quot;data&amp;quot; by typing the description&lt;br /&gt;
&amp;quot;:mary/data&amp;quot;. Using the current directory method simply saves typing,&lt;br /&gt;
because all you have to do is specify the filename &amp;quot;data&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To obtain the other files on the disk, first type &amp;quot;:bill/text&amp;quot; and&lt;br /&gt;
&amp;quot;:bill/letter&amp;quot;, respectively. Another way might be to CD or type / before&lt;br /&gt;
the filename. Slash [(/)] does not mean &amp;quot;root&amp;quot; as in some systems, but&lt;br /&gt;
refers to the directory above the current directory. AmigaDOS allows&lt;br /&gt;
multiple slashes. Each slash refers to the level above. So a UNIX ../ is a&lt;br /&gt;
/ in AmigaDOS. Similarly, an MS-DOS ..\\ is a / in AmigaDOS. Thus, if the&lt;br /&gt;
current directory is &amp;quot;:mary/letter&amp;quot;, you may specify the file&lt;br /&gt;
&amp;quot;:mary/invoice/jun18&amp;quot; as &amp;quot;/invoice/jun18&amp;quot;. To refer to the files in&lt;br /&gt;
&amp;quot;:bill&amp;quot;, you could type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CD :bill&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CD //bill&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you could specify any file in &amp;quot;bill&amp;quot; with a single filename. Of&lt;br /&gt;
course, you could always use the // feature to refer directly to a&lt;br /&gt;
specific file. For example,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TYPE //bill/letter&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
displays the file without your first setting &amp;quot;bill&amp;quot; as the current&lt;br /&gt;
directory. To go straight to the root level, always type a colon (:)&lt;br /&gt;
followed by a directory name. If you use slashes, you must know the exact&lt;br /&gt;
numbewr of levels back desired.&lt;br /&gt;
&lt;br /&gt;
== Setting the Current Device ==&lt;br /&gt;
&lt;br /&gt;
Finally, you may have many disk drives. Each [floppy] disk device has a name in the form DFn (for example DF1), where the &amp;quot;n&amp;quot; refers to the number of the device. (Currently, AmigaDOS accepts the device names DF0 to DF3.) Each individual disk is also associated with a unique name, known as a volume name (see below for more details).&lt;br /&gt;
&lt;br /&gt;
In addition, the logical device SYS: is assigned to the disk you started the system up from. You can use this name in place of a disk device name (like DF0:).&lt;br /&gt;
&lt;br /&gt;
The current directory is also associated with a current drive, the drive where you may find the [current] directory. As you know, prefacing a file description with a colon serves to identify the root of the current drive. However, to give the root directory of a specific drive, you precede the colon  with the drive name. Thus, you have yet another way of specifying the file &amp;quot;data&amp;quot; in directory &amp;quot;mary&amp;quot;, that is &amp;quot;DF1:mary/data&amp;quot;. This assumes that you have inserted the disk into drive DF1. So, to reference a file on the drive DF0 called &amp;quot;project-report&amp;quot; in directory &amp;quot;peter&amp;quot;, you would type &amp;quot;DF0:peter/project-report&amp;quot;, no matter which directory you had set as the current one.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=When you refer to a disk drive or any other device, on its own or with a directory name, you should always type the colon; for example, DF1:.}}&lt;br /&gt;
&lt;br /&gt;
This table illustrates the structure of a file description&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Left of the :&lt;br /&gt;
! Right of the :&lt;br /&gt;
! Right of a /&lt;br /&gt;
|-&lt;br /&gt;
| Device name or Volume name&lt;br /&gt;
| Directory name or Filename&lt;br /&gt;
| Subdirectory name or Filename&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Here are some examples of valid file descriptions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SYS:commands&lt;br /&gt;
DF0:bill&lt;br /&gt;
DF1:mary/letter&lt;br /&gt;
DF2:mary/letter/jun18&lt;br /&gt;
DOC:report/section1/figure&lt;br /&gt;
FONTS:silly-font&lt;br /&gt;
C:cls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To gain access to a file on a particular disk, you can type its unique name, which is known as the disk&#039;s volume name, instead of the device&lt;br /&gt;
name. For instance, if the file is on the disk &amp;quot;MCC&amp;quot;, you can specify the same file by typing the name &amp;quot;MCC:peter/project-report&amp;quot;. You can use the volume name to refer to a disk regardless of the drive it is in. You assign a volume name to a disk when you format it. You can also change the volume name using the RELABEL command.&lt;br /&gt;
&lt;br /&gt;
A device name, unlike a volume name, is not really part of the name. For example, AmigaDOS can read a file you created on DF0: from another drive, such as DF1:, if you place the disk in that drive, assuming of course that the drives are interchangeable. That is, if you created a file called &amp;quot;bill&amp;quot; on a disk in drive DF0:, the file is known as &amp;quot;DF0:bill&amp;quot;. If you then move the disk to drive DF1:, AmigaDOS can still read the file, which is then known as &amp;quot;DF1:bill&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Attaching a Filenote ==&lt;br /&gt;
&lt;br /&gt;
Although a filename can give some information about its contents, it is&lt;br /&gt;
often necessary to look in the file itself to find out more. AmigaDOS&lt;br /&gt;
provides a simple solution to this problem. You can use the command called&lt;br /&gt;
FILENOTE to attach an associated comment. You can make up a comment of up&lt;br /&gt;
to 80 characters (you must enclose comments containing spaces in double&lt;br /&gt;
quotes). Anything can be put in a file comment: the day of the file&#039;s&lt;br /&gt;
creation, whether or not a bug has been fixed, the version number of a&lt;br /&gt;
program, and anything else that may help to identify it.&lt;br /&gt;
&lt;br /&gt;
You must associate a comment with a particular file - not all files have&lt;br /&gt;
them. To attach comments, you use the FILENOTE command. If you create a&lt;br /&gt;
new file, it will not have a comment. Even if the new file is a copy of a&lt;br /&gt;
file that has a comment, the comment is not copied to the new file.&lt;br /&gt;
However, any comment attached to a file which is overwritten is retained.&lt;br /&gt;
To write a program to copy a file and its comment, you&#039;ll have to do some&lt;br /&gt;
extra work to copy the comment.&lt;br /&gt;
&lt;br /&gt;
When you rename a file, the comment associated with it doesn&#039;t change.&lt;br /&gt;
The RENAME command only change the name of the file. The file&#039;s contents&lt;br /&gt;
and comment remain the same regardless of the name change.&lt;br /&gt;
&lt;br /&gt;
== Understanding Device Names ==&lt;br /&gt;
&lt;br /&gt;
Devices have names so that you can refer to them by name. Disk names such as DF0: are examples of device names. Note that you may refer to device names, like filenames, using either upper or lower case. For disks, you follow the device name by a filename because AmigaDOS supports files on these devices. Furthermore, the filename can include directories because AmigaDOS also supports directories.&lt;br /&gt;
&lt;br /&gt;
You can also create files in memory with the device called RAM:. RAM: implements a filing system in memory that supports any of the normal filing system comands.&lt;br /&gt;
&lt;br /&gt;
Once the RAM: device exists, you can, for instance, create a directory to copy all the commands into memory. To do this, type the following&lt;br /&gt;
commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
MAKEDIR ram:c&lt;br /&gt;
COPY sys:c TO ram:c&lt;br /&gt;
ASSIGN C: ram:c&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You could then look at the output with DIR RAM:. It would include the direcotry &amp;quot;c&amp;quot; (DIR lists this as &amp;quot;c (dir)&amp;quot;). This would make loading commands very quick but would leave little room in memory for anything else. Any files in the RAM: device are lost when you reset the machine.&lt;br /&gt;
&lt;br /&gt;
AmigaDOS also provides a number of other devices that you can use instead of a reference to a disk file. The following paragraphs describe&lt;br /&gt;
these devices including NIL:, SER:, PAR:, PRT:, CON:, and RAW:. In particular, the device NIL: is a dummy device. AmigaDOS simply throws away&lt;br /&gt;
output written to NIL:. While reading from NIL:, AmigaDOS gives an immediate &amp;quot;end-of-file&amp;quot; indication. For example, you would type the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
EDIT abc TO nil:&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to use the editor to browse through a file, while AmigaDOS throws away the edited output.&lt;br /&gt;
&lt;br /&gt;
You use the device called SER: to refer to any device connected to the serial line (often a [modem or] printer). Thus, you would type the following command sequence:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
COPY xyz TO ser:&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to instruct AmigaDOS to send the contents of the file &amp;quot;xyz&amp;quot; down the serial line. Note that the serial device only copies in multiples of 400&lt;br /&gt;
bytes at a time. Copying with SER: can therefore appear granular.&lt;br /&gt;
&lt;br /&gt;
The device PAR: refers to the parallel port in the same way.&lt;br /&gt;
&lt;br /&gt;
AmigaDOS also provides the device PRT: (for PRinTer). PRT: is the printer you chose in the Preferences program. In this program, you can define your printer to be connected through either the serial or parallel port. Thus, the command sequence:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
COPY xyz TO prt:&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
prints the file &amp;quot;xyz&amp;quot;, no matter how the printer is connected.&lt;br /&gt;
&lt;br /&gt;
All output sent to PRT: is translated through the printer driver selected in Preferences. The printer driver will translate standard ANSI&lt;br /&gt;
escape codes into the specific code required by the printer. PRT: translates every linefeed character in a file to carriage return plus linefeed. Some printers, however, require files without translation. To send a file with the linefeeds as just linefeeds, you use PRT:RAW instead of PRT:.&lt;br /&gt;
&lt;br /&gt;
AmigaDOS supports multiple windows. To make a new window, you can specify the device CON:. The format for CON: is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CON:x/y/width/height/[title]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;quot;x&amp;quot; and &amp;quot;y&amp;quot; are coordinates, &amp;quot;width&amp;quot; and &amp;quot;height&amp;quot; are integers describing the width and height of the new window, and &amp;quot;title&amp;quot;, which is&lt;br /&gt;
optional, is a string. The title appears on the window&#039;s title bar. You must include all the slashes, including the last one. Your title can&lt;br /&gt;
include up to 30 characters (including spaces). If the title has spaces, you must enclose the whole description in double quotes (&amp;quot;) as shown in&lt;br /&gt;
the following example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;quot;CON:20/10/300/100/my window&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There is another window device called RAW:, but it is of little use to the general user. You can use RAW: to create a raw window device similar to CON:. However, unlike CON:, RAW: does no character translation and does not allow you to change the contents of a line. That is to say, RAW: accepts input and returns output in exactly the same form that it was originally typed. This means characters are sent to a program immediately without letting you erase anything with the BACKSPACE key. You usually use RAW: from a program where you might want to do input and output without character translation.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=WARNING|text=RAW: is intended for the advanced user. Do not use RAW: experimentally.}}&lt;br /&gt;
&lt;br /&gt;
There is one special name, which is * (asterisk). You use this to refer to the current window, both for input or for output. You can use the COPY command to copy from one file to another. Using *, you can copy from the current window to another window, for example,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
COPY * TO CON:20/20/350/150/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
from the current window to the current window, for example,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
COPY * TO *&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
or from a file to the current window, for example,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
COPY bill/letter TO *&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
AmigaDOS finishes copying when it comes to the end of the file. To tell AmigaDOS to stop copying from *, you must give the Ctrl-\ combination.&lt;br /&gt;
Note that * is NOT the universal wild card.&lt;br /&gt;
&lt;br /&gt;
== Using Directory Conventions and Logical Devices ==&lt;br /&gt;
&lt;br /&gt;
In addition to the aforementioned physical devices, AmigaDOS supports a&lt;br /&gt;
variety of useful &#039;&#039;logical devices&#039;&#039;. AmigaDOS uses these devices to find&lt;br /&gt;
the files that your programs require from time to time. (So that your&lt;br /&gt;
programs can refer to a standard device name regardless of where the file&lt;br /&gt;
actually is.) All of these &amp;quot;logical devices&amp;quot; may be reassigned by you to&lt;br /&gt;
reference any directory.&lt;br /&gt;
&lt;br /&gt;
The logical devices described in this section are as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Logical Device Name&lt;br /&gt;
! Description&lt;br /&gt;
! Directory&lt;br /&gt;
|-&lt;br /&gt;
| SYS: || System disk root directory || SYS:&lt;br /&gt;
|-&lt;br /&gt;
| C: || Commands directory || SYS:C&lt;br /&gt;
|-&lt;br /&gt;
| L: || Library directory || SYS:L&lt;br /&gt;
|-&lt;br /&gt;
| S: || Script directory || SYS:S&lt;br /&gt;
|-&lt;br /&gt;
| LIBS: || Directory for OpenLibrary() calls || SYS:LIBS&lt;br /&gt;
|-&lt;br /&gt;
| DEVS: || Directory for OpenDevice() calls || SYS:DEVS&lt;br /&gt;
|-&lt;br /&gt;
| FONTS: || Loadable fonts for OpenFonts() calls || SYS:FONTS&lt;br /&gt;
|-&lt;br /&gt;
| T: || Temporary workspace || RAM:T&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== SYS: ===&lt;br /&gt;
&lt;br /&gt;
Typical directory name: Workbench:&lt;br /&gt;
&lt;br /&gt;
&amp;quot;SYS&amp;quot; represents the &#039;&#039;&#039;SYS&#039;&#039;&#039;tem disk root directory. When you first start up&lt;br /&gt;
the Amiga system [from floppy], AmigaDOS assigns SYS: to the root&lt;br /&gt;
directory name of the disk in DF0:. If, for instance, the disk in drive&lt;br /&gt;
DF0: has the volume name Workbench, then AmigaDOS assigns SYS: to&lt;br /&gt;
Workbench:. After this assignment, any programs that refer to SYS: use&lt;br /&gt;
that disk&#039;s root directory.&lt;br /&gt;
&lt;br /&gt;
=== C: ===&lt;br /&gt;
&lt;br /&gt;
Typical directory name: Workbench:C&lt;br /&gt;
&lt;br /&gt;
&amp;quot;C&amp;quot; represents the &#039;&#039;&#039;C&#039;&#039;&#039;ommands direcotry. When you type a command to the CLI&lt;br /&gt;
(DIR, for example), AmigaDOS first searches for that command in your&lt;br /&gt;
cuurent directory. If the system cannot find the command in the current&lt;br /&gt;
directory, it then look for &amp;quot;C:DIR&amp;quot;. So that, if you have assigned &amp;quot;C:&amp;quot; to&lt;br /&gt;
another directory (for example, &amp;quot;Boot_disk.c&amp;quot;), AmigaDOS reads and&lt;br /&gt;
executes from &amp;quot;Boot_disk:c/DIR&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== L: ===&lt;br /&gt;
&lt;br /&gt;
Typical directory name: Workbench:L&lt;br /&gt;
&lt;br /&gt;
&amp;quot;L&amp;quot; represents the &#039;&#039;&#039;L&#039;&#039;&#039;ibrary directory. This directory keeps the overlays&lt;br /&gt;
for large commands and nonresident parts of the operating system. For&lt;br /&gt;
instance, the disk-based run-time libraries (Aux-Handler, Port-Handler,&lt;br /&gt;
and so forth) are kept here. AmigaDOS requires this directory to operate.&lt;br /&gt;
&lt;br /&gt;
=== S: ===&lt;br /&gt;
&lt;br /&gt;
Typical directory name: Workbench:S&lt;br /&gt;
&lt;br /&gt;
&amp;quot;S&amp;quot; represents the &#039;&#039;&#039;S&#039;&#039;&#039;cript directory. This directory contains command&lt;br /&gt;
scripts that the EXECUTE command searches for and uses. EXECUTE first&lt;br /&gt;
looks for the script (or batch) file in your current directory. If EXECUTE&lt;br /&gt;
cannot find it there, it looks in the directory that you have assigned S:&lt;br /&gt;
to.&lt;br /&gt;
&lt;br /&gt;
=== LIBS: ===&lt;br /&gt;
&lt;br /&gt;
Typical directory name: Workbench:Libs&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;OpenLibrary() looks here for the library if it is not already loaded in memory.&lt;br /&gt;
&lt;br /&gt;
=== DEVS: ===&lt;br /&gt;
&lt;br /&gt;
Typical directory name: Workbench:Devs&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;OpenDevice() looks here for the device if it is not already loaded in memory.&lt;br /&gt;
&lt;br /&gt;
=== FONTS: ===&lt;br /&gt;
&lt;br /&gt;
Typical directory name: Workbench:Fonts&lt;br /&gt;
&lt;br /&gt;
OpenFonts() looks here for your loadable fonts if they are not already loaded in memory.&lt;br /&gt;
&lt;br /&gt;
=== T: ===&lt;br /&gt;
&lt;br /&gt;
Typical directory name: RAM:T&lt;br /&gt;
&lt;br /&gt;
You use this directory to store temporary files. Programs such as editors place their temporary work files, or backup copies of the last file edited, in this directory. If you run out of space on a disk, this is one of the first places you should look for files that are no longer needed.&lt;br /&gt;
&lt;br /&gt;
=== During booting ===&lt;br /&gt;
&lt;br /&gt;
When the system is first booted, AmigaDOS initially assigns C: to the :C&lt;br /&gt;
directory. This means that if you boot with a disk that you had formatted&lt;br /&gt;
by issuing the command:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FORMAT DRIVE DF0: NAME &amp;quot;My.Boot.Disk&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SYS: is assigned to &amp;quot;My.Boot.Disk&amp;quot;. The &amp;quot;logical device&amp;quot; C: is assigned to&lt;br /&gt;
the C: directory on the same disk (that is, My.Boot.Disk:c). Likewise, the&lt;br /&gt;
following assignments are made:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
C:             My.Boot.Disk:c&lt;br /&gt;
L:             My.Boot.Disk:l&lt;br /&gt;
S:             My.Boot.Disk:s&lt;br /&gt;
LIBS:          My.Boot.Disk:libs&lt;br /&gt;
DEVS:          My.Boot.Disk:devs&lt;br /&gt;
FONTS:         My.Boot.Disk:fonts&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a directory is not present, the corresponding logical device is&lt;br /&gt;
assigned to the root directory.&lt;br /&gt;
&lt;br /&gt;
If you have a non-bootable hard disk (here called DH0:) and you want to&lt;br /&gt;
use the system files on it, you must issue the following commands to the&lt;br /&gt;
system:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ASSIGN SYS:    DH0:&lt;br /&gt;
ASSIGN C:      DH0:c&lt;br /&gt;
ASSIGN L:      DH0:l&lt;br /&gt;
ASSIGN S:      DH0:s&lt;br /&gt;
ASSIGN LIBS:   DH0:libs&lt;br /&gt;
ASSIGN DEVS:   DH0:devs&lt;br /&gt;
ASSIGN FONTS:  DH0:fonts&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If your hard disk is bootable, you don&#039;t need to make these assigns since&lt;br /&gt;
the system handles it for you.&lt;br /&gt;
&lt;br /&gt;
Please keep in mind that assignments are global to all Shell processes.&lt;br /&gt;
Changing an assignment within one window changes it for all windows.&lt;br /&gt;
&lt;br /&gt;
If you want to use your own special font library, type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ASSIGN FONTS: &amp;quot;Special font disk:myfonts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want your commands to load faster (and you have memory &amp;quot;to burn&amp;quot;),&lt;br /&gt;
type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
MAKEDIR ram:c&lt;br /&gt;
COPY sys:c ram:c ALL&lt;br /&gt;
ASSIGN c: ram:c&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This copies all of the normal AmigaDOS commands to the RAM disk and&lt;br /&gt;
reassigns the commands directoy so that the system finds them there.&lt;br /&gt;
Another way to speed up AmigaDOS commands is by making them resident.&lt;br /&gt;
See the description of the RESIDENT command.&lt;br /&gt;
&lt;br /&gt;
= Using AmigaDOS Commands =&lt;br /&gt;
&lt;br /&gt;
An AmigaDOS command consists of the command name and its arguments, if any. To execute an AmigaDOS command, you type the command name and its&lt;br /&gt;
arguments after the Shell prompt.&lt;br /&gt;
&lt;br /&gt;
When you type a command name, the command runs as part of the Command Line Interface (Shell). You can type other command names ahead, but AmigaDOS does not execute them until the current command has finished. When a command has finished, the current Shell prompt appears. In this&lt;br /&gt;
case, the command is running interactively.&lt;br /&gt;
&lt;br /&gt;
The Shell prompt is initially n&amp;gt; where n is the number of the Shell process. However, it can be changed to something else with the PROMPT&lt;br /&gt;
command.&lt;br /&gt;
&lt;br /&gt;
{{Note|title=WARNING|text=If you run a command interactively and it fails, AmigaDOS continues to execute the next command you typed anyway. Therefore, it can be dangerous to type many commands ahead. For example, if you type&amp;lt;br/&amp;gt;&lt;br /&gt;
COPY a TO b&amp;lt;br/&amp;gt;&lt;br /&gt;
DELETE a&amp;lt;br/&amp;gt;&lt;br /&gt;
and the COPY command fails (perhaps because the disk is full), then DELETE executes and you lose your file.}}&lt;br /&gt;
&lt;br /&gt;
== Running Commands in the Background ==&lt;br /&gt;
&lt;br /&gt;
You can instruct AmigaDOS to run a command, or commands, in the&lt;br /&gt;
background. To do this, you use the RUN command. This creates a new Shell&lt;br /&gt;
as a separate process of the same priority. In this case, AmigaDOS&lt;br /&gt;
executes subsequent command lines at the same time as those that have been&lt;br /&gt;
RUN. For example, you can examine the contents of your directory at the&lt;br /&gt;
same time as sending a copy of your text file to the printer. To do this,&lt;br /&gt;
type&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RUN TYPE text_file TO prt:&lt;br /&gt;
LIST&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
RUN creates a new Shell and carries out your printing while you list your&lt;br /&gt;
directory files on your original Shell window.&lt;br /&gt;
&lt;br /&gt;
You can ask AmigaDOS to carry out several commands using RUN. RUN takes&lt;br /&gt;
each command and carries it out in the given order. The line containing&lt;br /&gt;
commands after RUN is called a command line. To terminate the command&lt;br /&gt;
line, press RETURN. To extend your command line over several lines, type a&lt;br /&gt;
plus sign (+) before pressing RETURN on every line except the last. For&lt;br /&gt;
example,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RUN JOIN text_file1 text_file2 AS text_file +&lt;br /&gt;
SORT text_file TO sorted_text +&lt;br /&gt;
TYPE sorted_text TO prt:&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to start a command using RUN and then close the Shell window&lt;br /&gt;
from which it was launched, you will have to redirect input and output. To&lt;br /&gt;
do this use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RUN &amp;lt;NIL: &amp;gt;NIL: command&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Executing Command Files ==&lt;br /&gt;
&lt;br /&gt;
You can also use the EXECUTE command to execute command lines in a file&lt;br /&gt;
instead of typing them in directly. The Shell reads the sequence of&lt;br /&gt;
commands from the file until it finds an error the end of the file. If it&lt;br /&gt;
finds an error, AmigaDOS does not execute subsequent commands on the RUN&lt;br /&gt;
line or in the file used by EXECUTE, unless you have used the FAILAT&lt;br /&gt;
command. The Shell only&lt;br /&gt;
gives prompts after executing commands that have run interactively.&lt;br /&gt;
&lt;br /&gt;
== Directing Command Input and Output ==&lt;br /&gt;
&lt;br /&gt;
AmigaDOS provides a way for you to redirect standard input and output. You&lt;br /&gt;
use the &amp;gt; and &amp;lt; symbols as commands. When you type a command, AmigaDOS&lt;br /&gt;
usually displays the output from that command on the screen. To tell&lt;br /&gt;
AmigaDOS to send the output to a file, you can use the &amp;gt; command. To tell&lt;br /&gt;
AmigaDOS to accept the input to a program from the specified file rather&lt;br /&gt;
than from the keyboard, you use the &amp;lt; command. The &amp;lt; and &amp;gt; commands act&lt;br /&gt;
like traffic cops who direct the flow of information. For example, to&lt;br /&gt;
direct the output from the DATE command and write it to the file named&lt;br /&gt;
&amp;quot;text_file&amp;quot;, you would type the following command line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DATE &amp;gt; text_file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to redirect output to a file that already exists use &amp;gt;&amp;gt;. For&lt;br /&gt;
example, to direct the output of the TYPE command to a file that already&lt;br /&gt;
exists use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TYPE &amp;gt;&amp;gt;more_text original_text&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The text stored in the file original_text will be appended to any text&lt;br /&gt;
that is already stored in the file my_text. Under 2.0 and later versions&lt;br /&gt;
of AmigaDOS, if you redirect output to a file using &amp;gt;&amp;gt; and the file does&lt;br /&gt;
not exist, then the file will be created for you.&lt;br /&gt;
&lt;br /&gt;
= Interrupting AmigaDOS =&lt;br /&gt;
&lt;br /&gt;
AmigaDOS allows you to indicate four levels of attention interrupt with Ctrl-C, Ctrl-D, Ctrl-E, and Ctrl-F. To stop the current command from&lt;br /&gt;
whatever it was doing, press Ctrl-C. In some cases, such as EDIT, pressing Ctrl-C instructs the command to stop what it was doing and then to return to reading more EDIT commands. To tell the CLI to stop a command sequence initiated by the EXECUTE command as soon as the current command being executed finishes, press Ctrl-D. Ctrl-E and Ctrl-F are only used by certain commands in special cases.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=It is the programmer&#039;s responsibility to detect and respond to these interruption flags. AmigaDOS will not kill a program by itself.}}&lt;br /&gt;
&lt;br /&gt;
= Restart Validation Process =&lt;br /&gt;
&lt;br /&gt;
When you first insert a disk for updating, AmigaDOS creates a process at&lt;br /&gt;
low priority. This validates the entire structure on the disk. Until the&lt;br /&gt;
restart process has completed this job, you cannot create files on the&lt;br /&gt;
disk. It is possible, however, to read files.&lt;br /&gt;
&lt;br /&gt;
Older versions of AmigaDOS (1.3 and earlier) do some additional&lt;br /&gt;
processing when a disk is inserted. When the restart process completes,&lt;br /&gt;
AmigaDOS checks to see if you have set the system date and time. To set&lt;br /&gt;
the date and time, you use the DATE command, the SETCLOCK command or the&lt;br /&gt;
TIME command. If you do not specify the system date,&lt;br /&gt;
AmigaDOS sets the system date to the date and time of the most recently&lt;br /&gt;
created file on the inserted disk. This ensures that newer versions of&lt;br /&gt;
files have more recent dates, even though the actual time and date will be&lt;br /&gt;
incorrect.&lt;br /&gt;
&lt;br /&gt;
= Commonly Used Commands =&lt;br /&gt;
&lt;br /&gt;
This manual describes the AmigaDOS and its commands. The Command Line&lt;br /&gt;
Interpreter (CLI) reads AmigaDOS commands typed into a CLI window and&lt;br /&gt;
translates them into actions performed by the computer. In this sense, the&lt;br /&gt;
CLI is similar to more &amp;quot;traditional&amp;quot; computer interfaces: you type in&lt;br /&gt;
commands and the interface displays text in return.&lt;br /&gt;
&lt;br /&gt;
== Using the Shell ==&lt;br /&gt;
&lt;br /&gt;
To use the Shell interface, select the Shell window and type the desired&lt;br /&gt;
Shell commands (described within this manual). The Shell window(s) may be&lt;br /&gt;
sized and moved just like many others. To close the Shell window, type&lt;br /&gt;
&amp;quot;ENDSHELL&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Not all programs or commands can be run under both the Workbench and the&lt;br /&gt;
Shell environment. Many of the AmigaDOS commands described in Chapter 2&lt;br /&gt;
can be run only from the Shell.&lt;br /&gt;
&lt;br /&gt;
Throughout this book, the terms &amp;quot;CLI&amp;quot; and &amp;quot;Shell&amp;quot; are used to refer to&lt;br /&gt;
the special window where you can type in AmigaDOS. Although the terms are&lt;br /&gt;
used to mean the same thing, there is a slight difference. Just keep in&lt;br /&gt;
mind that the CLI and the Shell both refer to the place where you can type&lt;br /&gt;
in AmigaDOS commands.&lt;br /&gt;
&lt;br /&gt;
= Some AmigaDOS Commands =&lt;br /&gt;
&lt;br /&gt;
Alhough all the commands that are available through the Shell are&lt;br /&gt;
explained in deleted in Chapter 2 of this book, we have found that most&lt;br /&gt;
users will use very few of the advanced options. Therefore we have&lt;br /&gt;
provided a summary here showing various commands in their most common&lt;br /&gt;
form.&lt;br /&gt;
&lt;br /&gt;
The commands summarized below (along with the actual AmigaDOS command&lt;br /&gt;
name) ask AmigaDOS to do such operations as:&lt;br /&gt;
&lt;br /&gt;
* Copy a disk (DISKCOPY)&lt;br /&gt;
* Format a new disk (FORMAT)&lt;br /&gt;
* Make a formatted disk bootable (INSTALL)&lt;br /&gt;
* Create a CLI disk&lt;br /&gt;
* Relabel a disk (RELABEL)&lt;br /&gt;
* Look at the directories of a disk (DIR)&lt;br /&gt;
* Get information about files (LIST)&lt;br /&gt;
* Prevent a file from accidental deletion (PROTECT)&lt;br /&gt;
* Get information about a file system (INFO)&lt;br /&gt;
* Change a current directory (CD)&lt;br /&gt;
* Set the date and time (DATE)&lt;br /&gt;
* Redirect the output of a command (&amp;gt;)&lt;br /&gt;
* Type a text file to the screen (TYPE)&lt;br /&gt;
* Rename a file (RENAME)&lt;br /&gt;
* Delete a file (DELETE)&lt;br /&gt;
* Create a new directory (MAKEDIR)&lt;br /&gt;
* Copy files on a dual-drive system (COPY)&lt;br /&gt;
* Copy files on a single-drive system (COPY)&lt;br /&gt;
* Find files on a disk (DIR OPT A)&lt;br /&gt;
* Do something automatically at boot time (using Startup-Sequence)&lt;br /&gt;
* Tell AmigaDOS where to look for certain things (ASSIGN)&lt;br /&gt;
* Open a new Shell window (NEWSHELL)&lt;br /&gt;
* Close an existing Shell window (ENDSHELL)&lt;br /&gt;
&lt;br /&gt;
== For a New User ==&lt;br /&gt;
&lt;br /&gt;
For a new user, we suggest that you read and try each of these items in&lt;br /&gt;
sequence. Each command that is shown below leaves a test disk in a known&lt;br /&gt;
state so that the command that immediately follows will work exactly as&lt;br /&gt;
shown. Later, when you are more familiar with the system, the subsection&lt;br /&gt;
titles shown below will serve to refresh your memory.&lt;br /&gt;
&lt;br /&gt;
== How to Begin ==&lt;br /&gt;
&lt;br /&gt;
Before you begin this section, be sure you have two blank, double-sided&lt;br /&gt;
disks, and your Workbench disk. Before you begin, write protect your&lt;br /&gt;
master disk, and write enable the blank disks. Most of the commands given&lt;br /&gt;
below assume that you have a single-drive system; however, for [the]&lt;br /&gt;
convenience of those with dual-drive systems, the dual-drive version of&lt;br /&gt;
the command is occasionally given.&lt;br /&gt;
&lt;br /&gt;
Commands that instruct AmigaDOS to execute are shown in the following&lt;br /&gt;
sections, indented from the left margin. After typing each command, press&lt;br /&gt;
the RETURN key to return control to AmigaDOS. Although the commands are&lt;br /&gt;
all shown in capital letters, this is simply to distinguish them from the&lt;br /&gt;
rest of the text. AmigaDOS will accept the commands in lower case as well&lt;br /&gt;
as upper case.&lt;br /&gt;
&lt;br /&gt;
In the sections that follow, the notations &amp;quot;DF0:&amp;quot; and &amp;quot;drive 0&amp;quot; refer to&lt;br /&gt;
the disk drive that is built into the Amiga. The notation &amp;quot;DF1:&amp;quot; refers to&lt;br /&gt;
the first external 3½-inch disk drive. (Some systems use &amp;quot;DF2:&amp;quot; to refer&lt;br /&gt;
to the [second] external drive.)&lt;br /&gt;
&lt;br /&gt;
You will occasionally see a semicolon [(;)] on a command line that you&lt;br /&gt;
are told to type. What follows the semicolon is treated as a comment by&lt;br /&gt;
AmigaDOS. Since AmigaDOS ignores the rest of the line, you don&#039;t need to&lt;br /&gt;
type the comment along with the command. It is for your information only.&lt;br /&gt;
&lt;br /&gt;
For most commands, you can get a very limited form of help by typing the&lt;br /&gt;
command name, followed by a question mark (?) and pressing RETURN. It&lt;br /&gt;
shows you the &amp;quot;template&amp;quot; of a command, containing the sequence of&lt;br /&gt;
parameters it expects and the keywords it recognizes.&lt;br /&gt;
&lt;br /&gt;
=== Copying a Disk ===&lt;br /&gt;
&lt;br /&gt;
You can use this sequence to back up your system master disk or any other&lt;br /&gt;
disk.&lt;br /&gt;
&lt;br /&gt;
For a one-disk system:&lt;br /&gt;
&lt;br /&gt;
    DISKCOPY FROM df0: TO df0:&lt;br /&gt;
&lt;br /&gt;
For a two-disk system:&lt;br /&gt;
&lt;br /&gt;
    DISKCOPY FROM df0: TO df1:&lt;br /&gt;
&lt;br /&gt;
Follow the instructions as they appear. For a single-drive system, you&#039;ll&lt;br /&gt;
be instructed to insert the master (FROM) disk. Then, as the copying&lt;br /&gt;
progresses, AmigaDOS asks you to insert the copy (TO) disk, swapping&lt;br /&gt;
master and copy in and out until all of the disk has been duplicated. For&lt;br /&gt;
a two-disk system, you&#039;ll be instructed to put the master disk into drive&lt;br /&gt;
DF0: (The built-in drive) and the copy disk onto which to copy into DF1:&lt;br /&gt;
(the first external drive).&lt;br /&gt;
&lt;br /&gt;
Remove your master disk and put your master disk in a safe place. Leave&lt;br /&gt;
the copy write-enabled so that you can store information on it. Insert the&lt;br /&gt;
copy you have just made into the built-in drive [(DF0:)] and reboot your&lt;br /&gt;
system from the copy. (See &amp;quot;Making a Disk Bootable&amp;quot;, below.)&lt;br /&gt;
  After the reboot, reenter the CLI mode again.&lt;br /&gt;
&lt;br /&gt;
=== Formatting a Disk ===&lt;br /&gt;
&lt;br /&gt;
To try this command, your Workbench or CLI disk copy should be in drive 0,&lt;br /&gt;
and you should have a blank disk available.&lt;br /&gt;
&lt;br /&gt;
Sometimes rather than simply copy a disk, you&#039;ll want to prepare a data&lt;br /&gt;
disk for your system. Then later you can copy selected files to this data&lt;br /&gt;
disk. Format your second blank disk by using the FORMAT command.&lt;br /&gt;
&lt;br /&gt;
    FORMAT DRIVE df0: NAME &amp;quot;AnyName&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Follow the instructions. You can format disks in either drive 0 (DF0:,&lt;br /&gt;
built into your Amiga) or an external drive.&lt;br /&gt;
&lt;br /&gt;
After the format is completed, wait for the disk activity light to go&lt;br /&gt;
off and remove the freshly formatted disk. Reinsert your Workbench. The&lt;br /&gt;
formatted disk can now be used to hold data files. It is not bootable,&lt;br /&gt;
however.&lt;br /&gt;
&lt;br /&gt;
=== Making a Disk Bootable ===&lt;br /&gt;
&lt;br /&gt;
To try this command, your Workbench disk copy should be in drive 0, and&lt;br /&gt;
your should have your freshly formatted disk available.&lt;br /&gt;
&lt;br /&gt;
A bootable disk is one that you can use to start up your Amiga following&lt;br /&gt;
the Kickstart process. You can change a formatted disk into a bootable&lt;br /&gt;
disk by typing the command:&lt;br /&gt;
&lt;br /&gt;
    INSTALL ?&lt;br /&gt;
&lt;br /&gt;
AmigaDOS responds:&lt;br /&gt;
&lt;br /&gt;
    DRIVE/A,NOBOOT/S,CHECK/S&lt;br /&gt;
&lt;br /&gt;
Remove your Workbench disk copy and insert the formatted disk. Then type:&lt;br /&gt;
&lt;br /&gt;
    df0:&lt;br /&gt;
&lt;br /&gt;
and press RETURN. AmigaDOS writes boot sectors to the disk in DF0:. Now,&lt;br /&gt;
if you wait until the disk activity light goes out, you can then perform a&lt;br /&gt;
full reset (Ctrl-Amiga-Amiga). When the system reboots, you will go&lt;br /&gt;
directly into the CLI rather than into the Workbench.&lt;br /&gt;
&lt;br /&gt;
Your formatted disk now contains a CLI and nothing else. This means that&lt;br /&gt;
although you see the interpreter, it can&#039;t perform any of the commands&lt;br /&gt;
shown in this section. A CLI needs several files before its commands can&lt;br /&gt;
be performed. All of the command files are located in the C directory of&lt;br /&gt;
your master disk.&lt;br /&gt;
&lt;br /&gt;
=== Making a CLI Disk ===&lt;br /&gt;
&lt;br /&gt;
There is another way to make a bootable disk that gives you a more useful&lt;br /&gt;
disk in that it leaves the CLI command directories intact. Here is a step-&lt;br /&gt;
by-step process to change a writable copy of a Workbench diskette into a&lt;br /&gt;
CLI disk:&lt;br /&gt;
&lt;br /&gt;
    1.  Copy your Workbench disk.&lt;br /&gt;
    2.  Open the Shell as described above.&lt;br /&gt;
    3.  Select the Shell window and type the command:&lt;br /&gt;
&lt;br /&gt;
    RENAME FROM s/startup-sequence TO s/NO-startup-sequence&lt;br /&gt;
&lt;br /&gt;
Now if you wait for the disk activity light to go off and perform a full&lt;br /&gt;
reset, your Workbench disk copy will have become a CLI disk. To restore&lt;br /&gt;
the Workbench, perform the rename again, but with the name sequence&lt;br /&gt;
reversed. You see, if AmigaDOS can&#039;t find a file with the exact name&lt;br /&gt;
&amp;quot;Startup-sequence&amp;quot; ins the &amp;quot;s&amp;quot; directory, it will enter command mode and&lt;br /&gt;
wait for you to type a command.&lt;br /&gt;
&lt;br /&gt;
=== Relabeling a Disk ===&lt;br /&gt;
&lt;br /&gt;
Before you try this command, your Workbench or CLI disk copy should be in&lt;br /&gt;
drive 0.&lt;br /&gt;
&lt;br /&gt;
If, after either copying or formatting a disk, you are not satisfied&lt;br /&gt;
with the volume name you have given it, you can change the name of the&lt;br /&gt;
volume by using the RELABEL command:&lt;br /&gt;
&lt;br /&gt;
    RELABEL df0: DifferentName&lt;br /&gt;
&lt;br /&gt;
This command changes the volume of the disk in drive DF0: to&lt;br /&gt;
&amp;quot;DifferentName&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Looking at the Directory ===&lt;br /&gt;
&lt;br /&gt;
Before you try this command, your Workbench or CLI disk copy should be in&lt;br /&gt;
drive 0.&lt;br /&gt;
&lt;br /&gt;
You look at the contents of a disk with [either of] the commands:&lt;br /&gt;
&lt;br /&gt;
    DIR or DIR df0:&lt;br /&gt;
&lt;br /&gt;
This lists the contents of your current directory. You can list the&lt;br /&gt;
contents of a different directory by specifying the pathname for that&lt;br /&gt;
directory. For example, the command:&lt;br /&gt;
&lt;br /&gt;
    DIR df0:c or DIR c&lt;br /&gt;
&lt;br /&gt;
lists the contents of the c (directory) on drive DF0:. Directories are&lt;br /&gt;
equivelent to the drawers you see when the Workbench screen is visible.&lt;br /&gt;
&lt;br /&gt;
You can look at the directory of a different disk unit, if you have one,&lt;br /&gt;
by specifying its name. For example:&lt;br /&gt;
&lt;br /&gt;
    DIR df1:&lt;br /&gt;
&lt;br /&gt;
lists the contents of a disk inserted in drive DF1:.&lt;br /&gt;
&lt;br /&gt;
You can even look at the directory of a disk that isn&#039;t currently in the&lt;br /&gt;
drive by specifying its volume name. For example, the contents of that&lt;br /&gt;
freshly formatted disk whose name we changed can be displayed by the&lt;br /&gt;
command:&lt;br /&gt;
&lt;br /&gt;
    DIR DifferentName:&lt;br /&gt;
&lt;br /&gt;
AmigaDOS will ask you to insert disk DifferentName into [any] drive so&lt;br /&gt;
that DIR can read it and report the contents of the directory. Don&#039;t do it&lt;br /&gt;
yet, however, because there are no files present for DIR to read. We&#039;ll&lt;br /&gt;
add some files later.&lt;br /&gt;
&lt;br /&gt;
=== Using the LIST Command ===&lt;br /&gt;
&lt;br /&gt;
To try this command, your Workbench or CLI disk copy should be in drive 0.&lt;br /&gt;
&lt;br /&gt;
The DIR command tells you the names of files that are in your directory.&lt;br /&gt;
The LIST command provides additional information about those files. Type&lt;br /&gt;
the command:&lt;br /&gt;
&lt;br /&gt;
    LIST&lt;br /&gt;
&lt;br /&gt;
AmigaDOS provides information about all files in the current directory,&lt;br /&gt;
including how large each file is, the protection flags for each file,&lt;br /&gt;
whether it is a file or a directory, and the date and time of its&lt;br /&gt;
creation.&lt;br /&gt;
&lt;br /&gt;
If you specify the name of a directory with LIST, it lists information&lt;br /&gt;
about the files within that directory:&lt;br /&gt;
&lt;br /&gt;
    LIST c&lt;br /&gt;
&lt;br /&gt;
In the second column after the filename you will see the protection flags&lt;br /&gt;
for the file. The acronym &amp;quot;rwed&amp;quot; refers to protection flags, for read,&lt;br /&gt;
write, execute, and delete. When each flag is set, using the PROTECT&lt;br /&gt;
command, a file is supposed to be readable, writable, executable, [and]&lt;br /&gt;
deletable.&lt;br /&gt;
&lt;br /&gt;
=== Using the PROTECT Command ===&lt;br /&gt;
&lt;br /&gt;
To try this command, your Workbench or CLI disk copy should be in drive 0.&lt;br /&gt;
This command protects (or unprotects) a file from being deleted&lt;br /&gt;
accidentally. Try the command&lt;br /&gt;
&lt;br /&gt;
    DATE &amp;gt;myfile&lt;br /&gt;
    PROTECT myfile&lt;br /&gt;
    LIST myfile&lt;br /&gt;
&lt;br /&gt;
You will see that all of the protect flags have been set to &amp;quot;--------&amp;quot;.&lt;br /&gt;
Now if you try:&lt;br /&gt;
&lt;br /&gt;
    DELETE myfile&lt;br /&gt;
&lt;br /&gt;
AmigaDOS responds&lt;br /&gt;
&lt;br /&gt;
    &amp;quot;Not deleted - file is protected from deletion&amp;quot;&lt;br /&gt;
&lt;br /&gt;
To reenable deletion of the file, type:&lt;br /&gt;
&lt;br /&gt;
    PROTECT myfile d&lt;br /&gt;
&lt;br /&gt;
For more information about file protection flags, see the discussion of&lt;br /&gt;
the PROTECT command.&lt;br /&gt;
&lt;br /&gt;
=== Getting Information About the File System ===&lt;br /&gt;
&lt;br /&gt;
Your Workbench or CLI disk copy should still be in drive 0. Type the&lt;br /&gt;
command&lt;br /&gt;
&lt;br /&gt;
    INFO&lt;br /&gt;
&lt;br /&gt;
It tells you how much space is used and how much is free on your disks,&lt;br /&gt;
whether they are read-only or read-write, and the name of the volume.&lt;br /&gt;
You can make more space on the disk by deleting files. You can change&lt;br /&gt;
the name of the volume by using the RELABEL command.&lt;br /&gt;
&lt;br /&gt;
If you want to get information about a disk that isn&#039;t in your single&lt;br /&gt;
drive at the moment, issue the command as&lt;br /&gt;
&lt;br /&gt;
    INFO ?&lt;br /&gt;
&lt;br /&gt;
AmigaDOS responds&lt;br /&gt;
&lt;br /&gt;
    DEVICE:&lt;br /&gt;
&lt;br /&gt;
AmigaDOS has loaded the INFO command from your CLI disk and shows you the&lt;br /&gt;
template for the command. The response &amp;quot;DEVICE:&amp;quot; says that you can enter&lt;br /&gt;
any device name to get information about it. But you don&#039;t have to type&lt;br /&gt;
anything other than a RETURN key to have it perform the command. Remove&lt;br /&gt;
your CLI disk and insert the disk on which you want INFO to operate. Wait&lt;br /&gt;
for the disk activity light to go on and off. Then press RETURN. AmigaDOS&lt;br /&gt;
gives you INFO about this other disk. This works for DIR as well as INFO.&lt;br /&gt;
&lt;br /&gt;
=== Changing Your Current Directory ===&lt;br /&gt;
&lt;br /&gt;
Until now, we have only stayed at the &amp;quot;root&amp;quot; or topmost hierarchical level&lt;br /&gt;
of the disk directory. You will find more information about the directory&lt;br /&gt;
tree structure in &amp;quot;Using Directories&amp;quot; earlier. To see the level at which&lt;br /&gt;
you are currently positioned in your directory tree, you use the command:&lt;br /&gt;
&lt;br /&gt;
    CD&lt;br /&gt;
&lt;br /&gt;
To change to a different current directory, you tell the system which&lt;br /&gt;
directory is to become the current one. For example, when you did a DIR&lt;br /&gt;
command on DF0:, the CLI disk, you say an entry &amp;quot;c (dir)&amp;quot;. If you want to&lt;br /&gt;
make this directory the current one, you issue the command:&lt;br /&gt;
&lt;br /&gt;
    CD df0:c&lt;br /&gt;
&lt;br /&gt;
Now when you issue the command DIR, it shows the contents of this level of&lt;br /&gt;
the filing system. The command CD (alone) shows you the name of your&lt;br /&gt;
current directory. You go up to the root directory (the top level) by&lt;br /&gt;
specifying:&lt;br /&gt;
&lt;br /&gt;
    CD :&lt;br /&gt;
&lt;br /&gt;
on the current volume (if you refer to your disks by volume name) or&lt;br /&gt;
&lt;br /&gt;
    CD df0:&lt;br /&gt;
&lt;br /&gt;
on the built-in drive.&lt;br /&gt;
&lt;br /&gt;
=== Setting the Date and Time ===&lt;br /&gt;
&lt;br /&gt;
You can set the AmigaDOS clock by using the DATE command:&lt;br /&gt;
&lt;br /&gt;
    DATE 12:00:00 12-oct-85&lt;br /&gt;
&lt;br /&gt;
Now the system clock counts up from this date and time. If your system has&lt;br /&gt;
a battery-backed, real-time clock, you can set it using the DATE command&lt;br /&gt;
as described followed by the command:&lt;br /&gt;
&lt;br /&gt;
    SETCLOCK SAVE&lt;br /&gt;
&lt;br /&gt;
The SETCLOCK SAVE command copies AmigaDOS time to the real-time clock.&lt;br /&gt;
&lt;br /&gt;
=== Redirecting the Output of a Command ===&lt;br /&gt;
&lt;br /&gt;
Before you try this command, your Workbench or CLI disk should be in drive&lt;br /&gt;
0.&lt;br /&gt;
&lt;br /&gt;
Normally the output of all commands goes to the monitor screen. You can&lt;br /&gt;
change where the system puts the output by using the redirect command &amp;quot;&amp;gt;&amp;quot;.&lt;br /&gt;
The forward arrow symbol means send the output towards this output&lt;br /&gt;
filename. Here&#039;s an example:&lt;br /&gt;
&lt;br /&gt;
    DATE &amp;gt;datefile&lt;br /&gt;
&lt;br /&gt;
Execute the command so that you can use the datefile described below. This&lt;br /&gt;
command creates (or overwrites) a file named &amp;quot;datefile&amp;quot; in your current&lt;br /&gt;
directory.&lt;br /&gt;
&lt;br /&gt;
=== Typing a Text File to the Screen ===&lt;br /&gt;
&lt;br /&gt;
You can see the contents of a text file by using the TYPE command:&lt;br /&gt;
&lt;br /&gt;
    TYPE datefile&lt;br /&gt;
&lt;br /&gt;
This command will display whatever you have in the specified file. If you&lt;br /&gt;
wish to stop the output momentarily to read something on the screen, press&lt;br /&gt;
the space bar. To restart it press the BACKSPACE key. If you wish to end&lt;br /&gt;
the TYPE command, hold down the Ctrl key, and press the C key.&lt;br /&gt;
&lt;br /&gt;
=== Changing the Name of a File ===&lt;br /&gt;
&lt;br /&gt;
Before you try this command, your Workbench or CLI disk copy should be in&lt;br /&gt;
drive 0.&lt;br /&gt;
&lt;br /&gt;
You can change the name of a file by using the RENAME command:&lt;br /&gt;
&lt;br /&gt;
    RENAME FROM datefile TO newname&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    RENAME dateile newname&lt;br /&gt;
&lt;br /&gt;
Now use TYPE to verify that the new new refers to the same contents.&lt;br /&gt;
&lt;br /&gt;
    TYPE newname&lt;br /&gt;
&lt;br /&gt;
Notice that the alternate form of the command doesn&#039;t require that you use&lt;br /&gt;
the FROM and TO. Most of the AmigaDOS commands have an alternate form,&lt;br /&gt;
abbreviated from that shown in this tutorial section. The longer form has&lt;br /&gt;
been used primarily to introduce you to what the command does. Be sure to&lt;br /&gt;
examine the summary pages to familiarize yourself with the alternate&lt;br /&gt;
command forms that are available.&lt;br /&gt;
&lt;br /&gt;
=== Deleting Files ===&lt;br /&gt;
&lt;br /&gt;
To try this command, your Workbench or CLI disk should be in drive 0.&lt;br /&gt;
&lt;br /&gt;
You may be working on several versions of a program or text file, and eventually wish to delete versions of that file that you don&#039;t need anymore. The DELETE command lets you erase files and releases the disk space to AmigaDOS for resuse.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=If you DELETE files, it is not always possible to retrieve them. Be certain that you really do wish to delete them.}}&lt;br /&gt;
&lt;br /&gt;
Here is a sample command sequence, that creates a file using the redirection command, types it to verify that it is really there, then deletes it.&lt;br /&gt;
&lt;br /&gt;
    DIR &amp;gt;directorystuff&lt;br /&gt;
    TYPE directorystuff&lt;br /&gt;
    DELETE directorystuff&lt;br /&gt;
    TYPE directorystuff&lt;br /&gt;
&lt;br /&gt;
To the final command in the above sequence, AmigaDOS responds:&lt;br /&gt;
&lt;br /&gt;
    Can&#039;t open directorystuff&lt;br /&gt;
&lt;br /&gt;
indicating that the file can&#039;t be found, because you deleted it.&lt;br /&gt;
&lt;br /&gt;
=== Copying Files ===&lt;br /&gt;
&lt;br /&gt;
Before you enter this command, your Workbench or CLI disk should be in&lt;br /&gt;
drive 0.&lt;br /&gt;
&lt;br /&gt;
On a dual-drive system, copying files is easy:&lt;br /&gt;
&lt;br /&gt;
    COPY FROM df0:sourcepath TO df1:destinationpath&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    COPY df0:sourcepath df1:destinationpath&lt;br /&gt;
&lt;br /&gt;
On a single-drive system, copying files is a little more complex. You&lt;br /&gt;
must copy certain files from your system diskette into the system memory.&lt;br /&gt;
This is also called the RAM: device, or RAM disk. Copy the file(s) to the&lt;br /&gt;
RAM disk, change your directory to the RAM disk, then copy from the RAM&lt;br /&gt;
disk onto the destination disk. Here is a sample sequence.&lt;br /&gt;
&lt;br /&gt;
Be sure your Workbench or CLI disk is in the internal disk drive. Issue&lt;br /&gt;
the commands:&lt;br /&gt;
&lt;br /&gt;
    COPY df0:c/cd ram:&lt;br /&gt;
    COPY df0:c/copy ram:&lt;br /&gt;
    CD ram:&lt;br /&gt;
&lt;br /&gt;
Insert the source data disk into the drive. (For this example, copy the&lt;br /&gt;
EXECUTE command from the Workbench or CLI disk, which is already in the&lt;br /&gt;
drive.) Type:&lt;br /&gt;
&lt;br /&gt;
    COPY df0:c/execute ram:execute&lt;br /&gt;
               or&lt;br /&gt;
    COPY df0:c/execute ram:&lt;br /&gt;
&lt;br /&gt;
Remove the source disk, and insert the destination disk into the drive.&lt;br /&gt;
The destination disk can be any Amiga disk that has been formatted. Type:&lt;br /&gt;
&lt;br /&gt;
    COPY ram:execute df0:execute&lt;br /&gt;
&lt;br /&gt;
The EXECUTE command has now been copied from the source disk to the&lt;br /&gt;
destination disk.&lt;br /&gt;
&lt;br /&gt;
Remove the destination disk and inset your CLI or Workbench disk again.&lt;br /&gt;
Type:&lt;br /&gt;
&lt;br /&gt;
    CD df0:&lt;br /&gt;
&lt;br /&gt;
and you are back where you started. The only other command you may want to&lt;br /&gt;
perform is:&lt;br /&gt;
&lt;br /&gt;
    DELETE ram:cd ram:copy ram:execute&lt;br /&gt;
&lt;br /&gt;
which releases the RAM disk memory to the system for other uses.&lt;br /&gt;
&lt;br /&gt;
=== Creating a New Directory ===&lt;br /&gt;
&lt;br /&gt;
You can create a new diretory (newdrawer) within the current directory by&lt;br /&gt;
using the MAKEDIR command:&lt;br /&gt;
&lt;br /&gt;
    MAKEDIR newdrawer&lt;br /&gt;
&lt;br /&gt;
Now if you issue the DIR command, you will see that there is an entry for:&lt;br /&gt;
&lt;br /&gt;
    newdrawer (dir)&lt;br /&gt;
&lt;br /&gt;
You can also use the RENAME command to move a file from one directory&lt;br /&gt;
(drawer) to another on the same disk:&lt;br /&gt;
&lt;br /&gt;
    MAKEDIR newdrawer&lt;br /&gt;
    RENAME FROM newname TO newdrawer/newname&lt;br /&gt;
&lt;br /&gt;
moves the file from the current directory into the newdrawer you have&lt;br /&gt;
created. To check tht it has really been moved, issue the command:&lt;br /&gt;
&lt;br /&gt;
    DIR&lt;br /&gt;
&lt;br /&gt;
Then type:&lt;br /&gt;
&lt;br /&gt;
    DIR newdrawer&lt;br /&gt;
&lt;br /&gt;
AmigaDOS looks in the newdrawer, and shows you that the file named&lt;br /&gt;
&amp;quot;newname&amp;quot; is there.&lt;br /&gt;
&lt;br /&gt;
=== Is My File Somewhere on This Disk? ===&lt;br /&gt;
&lt;br /&gt;
Before you enter this command, your Workbench or CLI disk copy should be&lt;br /&gt;
in drive 0.&lt;br /&gt;
&lt;br /&gt;
Sometimes you wish to see everything on the disk, instead of only one&lt;br /&gt;
directory at a time. You can use the DIR command with one of its options:&lt;br /&gt;
&lt;br /&gt;
    DIR OPT A&lt;br /&gt;
&lt;br /&gt;
which lists all directories and subdirectories on the disk. Keep in mind&lt;br /&gt;
the space/BACKSPACE combination to pause and restart the listing.&lt;br /&gt;
&lt;br /&gt;
To get a closer look at the disk&#039;s contents, you might redirect the&lt;br /&gt;
output to a file:&lt;br /&gt;
&lt;br /&gt;
    DIR &amp;gt;mydiskdir ALL&lt;br /&gt;
&lt;br /&gt;
Notice that the redirect-the-output command character [(&amp;gt;)] and filename&lt;br /&gt;
MUST come before the list of options for the DIR command.&lt;br /&gt;
&lt;br /&gt;
Now, if you wish, you can TYPE the file mydiskdir and press the space&lt;br /&gt;
bar to pause the listing. Use the RETURN [or BACKSPACE] key to resume the&lt;br /&gt;
listing. Or, you can use ED to view the file, as follows:&lt;br /&gt;
&lt;br /&gt;
    ED mydiskdir&lt;br /&gt;
&lt;br /&gt;
Use the cursor keys to move up and down in the file.&lt;br /&gt;
&lt;br /&gt;
Use the key combination Esc then T &amp;lt;RETURN&amp;gt; to move to the top of the&lt;br /&gt;
file. Such a combination can be referred to as &amp;quot;Esc-T&amp;quot;, meaning Esc&lt;br /&gt;
followed by T.&lt;br /&gt;
&lt;br /&gt;
Use the key combination Esc-B &amp;lt;RETURN&amp;gt; to move to the bottom of the&lt;br /&gt;
file.&lt;br /&gt;
&lt;br /&gt;
Use the key combination Esc-M then a number &amp;lt;RETURN&amp;gt; to move to a&lt;br /&gt;
specific line number within the file.&lt;br /&gt;
&lt;br /&gt;
Use the key combination Esc-Q &amp;lt;RETURN&amp;gt; to quit without changing the&lt;br /&gt;
file; or&lt;br /&gt;
&lt;br /&gt;
Use Esc-X &amp;lt;RETURN&amp;gt; to write any changes to your file back into the&lt;br /&gt;
original filename.&lt;br /&gt;
&lt;br /&gt;
=== Automating the Boot Sequence ===&lt;br /&gt;
&lt;br /&gt;
There is a file in the &amp;quot;S&amp;quot; directory on your Workbench or CLI disk called startup-sequence. This is a script file. It contains a sequence of CLI&lt;br /&gt;
commands that AmigaDOS performs whenever you reboot the system. Also in your Workbench disk startup-sequence are LOADWB (load the Workbench&lt;br /&gt;
program) and ENCLI which basically leaves the Workbench program in control. You can make up your own startup-sequence file using NotePad to create a custom version of an EXECUTE command sequence. The EXECUTE command summary and tutorial section has details about various commands that you can have in this file. Note that startup-sequence can also be used to auto-run a program.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=The startup-sequence looks for a file called s:user-startup and executes it if one is found. Whenever possible, place all your startup additions and assignments in a file called s:user-startup rather than modifying the s:startup-sequence.}}&lt;br /&gt;
&lt;br /&gt;
=== Assigning a Disk ===&lt;br /&gt;
&lt;br /&gt;
Before you enter this command, your Workbench or CLI disk copy should be&lt;br /&gt;
in drive 0.&lt;br /&gt;
&lt;br /&gt;
Occasionally, you might wish to change to a different disk and then&lt;br /&gt;
continue your work. For example, you may have booted the system using a&lt;br /&gt;
Workbench disk, then wish to change to a CLI disk. If the CLI disk has a&lt;br /&gt;
directory on it that contains the executable commands you want to perform&lt;br /&gt;
(for example, a &amp;quot;c&amp;quot; directory), you can change to that disk by using the&lt;br /&gt;
ASSIGN command.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t use ASSIGN, you will have to swap disks to get commands&lt;br /&gt;
done. Here is an example. The intent is to change disks and begin using&lt;br /&gt;
&amp;quot;mydisk:&amp;quot; as the main disk. Before you begin, you must first create a disk&lt;br /&gt;
called &amp;quot;mydisk&amp;quot;. To do this, make a copy of Workbench (refer to the&lt;br /&gt;
instructions for copying a disk given at the beginning of this section).&lt;br /&gt;
Then use the RELABEL command described earlier to change the name of the&lt;br /&gt;
new copy to &amp;quot;mydisk&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    CD mydisk:&lt;br /&gt;
&lt;br /&gt;
AmigaDOS responds &amp;quot;Insert volume mydisk into any drive&amp;quot;. Insert it, then&lt;br /&gt;
type:&lt;br /&gt;
&lt;br /&gt;
    DIR&lt;br /&gt;
&lt;br /&gt;
AmigaDOS prompts &amp;quot;Insert volume Workbench [or whatever the boot disk name&lt;br /&gt;
was] into any drive&amp;quot;. It knows, from boot time, that the DIR command is in&lt;br /&gt;
the boot disk&#039;s &amp;quot;c&amp;quot; directory. AmigaDOS reads the DIR command, then asks&lt;br /&gt;
&amp;quot;insert mydisk in any drive&amp;quot;. Any other AmigaDOS command also results in&lt;br /&gt;
the need for a disk swap. To avoid this, use the ASSIGN command as&lt;br /&gt;
follows:&lt;br /&gt;
&lt;br /&gt;
    ASSIGN c: mydisk:c&lt;br /&gt;
&lt;br /&gt;
AmigaDOS asks &amp;quot;Insert volume mydisk into any drive&amp;quot;. From now on, all&lt;br /&gt;
commands  to AmigaDOS will be sought from the command (&amp;quot;c&amp;quot;) directory of&lt;br /&gt;
mydisk and AmigaDOS won&#039;t ask for the original disk back for simple&lt;br /&gt;
commands.&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve done this, you&#039;ll probably want to type:&lt;br /&gt;
&lt;br /&gt;
    CD mydisk:&lt;br /&gt;
&lt;br /&gt;
There are other things that AmigaDOS can assign. If you issue the command:&lt;br /&gt;
&lt;br /&gt;
    ASSIGN LIST&lt;br /&gt;
&lt;br /&gt;
you will see the other things as well. If you run a program that requires&lt;br /&gt;
a serial device (modem, printer) or a parallel device (printer), AmigaDOS&lt;br /&gt;
looks in the directory currently assigned to DEVS: to locate the device.&lt;br /&gt;
If all the system directories are on this new main disk, you can avoid&lt;br /&gt;
having AmigaDOS ask you to reinsert the original disk by providing a&lt;br /&gt;
script file on your disks that reassigns all devices to that disk. The&lt;br /&gt;
contents of this script file for a disk named &amp;quot;mydisk&amp;quot; are as follows:&lt;br /&gt;
&lt;br /&gt;
    ASSIGN SYS:   mydisk:&lt;br /&gt;
    ASSIGN S:     mydisk:s&lt;br /&gt;
    ASSIGN DEVS:  mydisk:devs&lt;br /&gt;
    ASSIGN L:     mydisk:l&lt;br /&gt;
    ASSIGN FONTS: mydisk:fonts&lt;br /&gt;
    ASSIGN LIBS:  mydisk:libs&lt;br /&gt;
&lt;br /&gt;
To create4 this script file, use the command:&lt;br /&gt;
&lt;br /&gt;
    COPY FROM * TO reassign&lt;br /&gt;
&lt;br /&gt;
Then type the above ASSIGN lines. After you&#039;ve typed the last line, enter&lt;br /&gt;
the key combination Ctrl-\\ which ends the file. The &amp;quot;*&amp;quot; stands for the&lt;br /&gt;
keyboard and current CLI window, so this method of creating a file is one&lt;br /&gt;
possible alternative to using ED or EDIT.&lt;br /&gt;
&lt;br /&gt;
Once you have created the script file, you can run it by typing:&lt;br /&gt;
&lt;br /&gt;
    EXECUTE reassign&lt;br /&gt;
&lt;br /&gt;
Now all the ASSIGNs that the system uses are set up for &amp;quot;mydisk&amp;quot; instead&lt;br /&gt;
of the original boot disk.&lt;br /&gt;
&lt;br /&gt;
= Closing Comments =&lt;br /&gt;
&lt;br /&gt;
Chapter 2 contains a reference section that shows the templates for each&lt;br /&gt;
of the commands in AmigaDOS. You can look at the description for each&lt;br /&gt;
command to find more information. Once you are familiar with the commands,&lt;br /&gt;
and the forms in which you can use them, the quick reference listing at&lt;br /&gt;
the end of the chapter will be useful to remind you of the commands that&lt;br /&gt;
are available.&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3421</id>
		<title>Exec Tasks</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3421"/>
		<updated>2012-07-10T17:52:19Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Disabling Tasks */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{NeedUpdate}}&lt;br /&gt;
== Exec Tasks ==&lt;br /&gt;
&lt;br /&gt;
One of the most powerful features of the Amiga operating system is its ability to run and manage multiple independent program tasks, providing each task with processor time based on their priority and activity. These tasks include system device drivers, background utilities, and user interface environments, as well as normal application programs. This multitasking capability is provided by the Exec library&#039;s management of task creation, termination, scheduling, event signals, traps, exceptions, and mutual exclusion.&lt;br /&gt;
&lt;br /&gt;
This section deals with Exec on a lower level than most applications programmers need and assumes you are already familiar with the Exec basics discussed in the [[Introduction_to_Exec|Introduction to Exec]].&lt;br /&gt;
&lt;br /&gt;
== Task Structure ==&lt;br /&gt;
&lt;br /&gt;
Exec maintains task context and state information in a task-control data structure. Like most Exec structures, Task structures are dynamically linked onto various task queues through the use of an embedded Exec list Node structure (see the Lists chapter). Any task can find its own task structure by calling FindTask(NULL). The C-language form of this structure is defined in the &amp;amp;lt;exec/tasks.h&amp;amp;gt; include file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Task  {&lt;br /&gt;
    struct Node tc_Node;&lt;br /&gt;
    UBYTE       tc_Flags;&lt;br /&gt;
    UBYTE       tc_State;&lt;br /&gt;
    BYTE        tc_IDNestCnt;   /* intr disabled nesting */&lt;br /&gt;
    BYTE        tc_TDNestCnt;   /* task disabled nesting */&lt;br /&gt;
    ULONG       tc_SigAlloc;    /* sigs allocated */&lt;br /&gt;
    ULONG       tc_SigWait;     /* sigs we are waiting for */&lt;br /&gt;
    ULONG       tc_SigRecvd;    /* sigs we have received */&lt;br /&gt;
    ULONG       tc_SigExcept;   /* sigs we will take excepts for */&lt;br /&gt;
    UWORD       tc_TrapAlloc;   /* traps allocated */&lt;br /&gt;
    UWORD       tc_TrapAble;    /* traps enabled */&lt;br /&gt;
    APTR        tc_ExceptData;  /* points to except data */&lt;br /&gt;
    APTR        tc_ExceptCode;  /* points to except code */&lt;br /&gt;
    APTR        tc_TrapData;    /* points to trap code */&lt;br /&gt;
    APTR        tc_TrapCode;    /* points to trap data */&lt;br /&gt;
    APTR        tc_SPReg;       /* stack pointer */&lt;br /&gt;
    APTR        tc_SPLower;     /* stack lower bound */&lt;br /&gt;
    APTR        tc_SPUpper;     /* stack upper bound + 2*/&lt;br /&gt;
    VOID      (*tc_Switch)();   /* task losing CPU */&lt;br /&gt;
    VOID      (*tc_Launch)();   /* task getting CPU */&lt;br /&gt;
    struct List tc_MemEntry;    /* allocated memory */&lt;br /&gt;
    APTR        tc_UserData;    /* per task data */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Most of these fields are not relevant for simple tasks; they are used by Exec for state and administrative purposes. A few fields, however, are provided for the advanced programs that support higher level environments (as in the case of &#039;&#039;processes&#039;&#039;) or require precise control (as in &#039;&#039;devices&#039;&#039;). The following sections explain these fields in more detail.&lt;br /&gt;
&lt;br /&gt;
== Task Creation ==&lt;br /&gt;
&lt;br /&gt;
To create a new task manually you must allocate a task structure, initialize its various fields, and then link it into Exec with a call to AddTask(). This is not the recommended way to create a task. This detailed information is only being provided for reference.&lt;br /&gt;
&lt;br /&gt;
The task structure may be allocated by calling the AllocVecTags() function with the MEMF_SHARED attribute and AVT_ClearWithValue tag.&lt;br /&gt;
&lt;br /&gt;
The Task fields that require initialization depend on how you intend to use the task. For the simplest of tasks, only a few fields must be initialized:&lt;br /&gt;
&lt;br /&gt;
; tc_Node&lt;br /&gt;
: The task list node structure. This includes the task&#039;s priority, its type, and its name (refer to [[Exec_Lists_and_Queues|Exec Lists and Queues]] chapter).&lt;br /&gt;
&lt;br /&gt;
; tc_SPLower&lt;br /&gt;
: The lower memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPUpper&lt;br /&gt;
: The upper memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPReg&lt;br /&gt;
: The initial stack pointer. Because task stacks grow &#039;&#039;downward&#039;&#039; in memory, this field is usually set to the same value as tc_SPUpper.&lt;br /&gt;
&lt;br /&gt;
Zeroing all other unused fields will cause Exec to supply the appropriate system default values. Allocating the structure with the AVT_ClearWithValue tag is an easy way to be sure that this happens.&lt;br /&gt;
&lt;br /&gt;
Once the structure has been initialized, it must be linked to Exec. This is done with a call to AddTask() in which the following parameters are specified:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
AddTask(struct Task *task, APTR initialPC, APTR finalPC )&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The task argument is a pointer to your initialized Task structure. Set initialPC to the entry point of your task code. This is the address of the first instruction the new task will execute.&lt;br /&gt;
&lt;br /&gt;
Set finalPC to the address of the finalization code for your task. This is a code section that will receive control if the initialPC routine ever performs a return. This exists to prevent your task from being launched into random memory upon an accidental return. The finalPC routine should usually perform various program-related clean-up duties and should then remove the task. If a zero is supplied for this parameter, Exec will use its default finalization code (which simply calls the RemTask() function).&lt;br /&gt;
&lt;br /&gt;
=== Task Creation With CreateTask() ===&lt;br /&gt;
&lt;br /&gt;
The recommended of creating a task is with CreateTask() or CreateTaskTags() functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
CreateTaskTags(CONST_STRPTR name, int32 priority, APTR initialPC, uint32 stacksize, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A task created with CreateTaskTags() may be removed with the DeleteTask() function, or it may simply return when it is finished. CreateTaskTags() adds a MemList to the tc_MemEntry of the task it creates, describing all memory it has allocated for the task, including the task stack and the Task structure itself. This memory will be deallocated by Exec when the task is either explicitly removed (RemTask() or DeleteTask()) or when it exits to Exec&#039;s default task removal code (RemTask()).&lt;br /&gt;
&lt;br /&gt;
Depending on the priority of a new task and the priorities of other tasks in the system, the newly added task may begin execution immediately.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Sharing Library Pointers&#039;&#039; Although in most cases it is possible for a parent task to pass a library base to a child task so the child can use that library, for some libraries, this is not possible. For this reason, the only library base sharable between tasks is Exec&#039;s library base.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Here is an example of simple task creation. In this example there is no coordination or communication between the main process and the simple task it has created. A more complex example might use named ports and messages to coordinate the activities and shutdown of two tasks. Because our task is very simple and never calls any system functions which could cause it to be signaled or awakened, we can safely remove the task at any time.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Keep This In Mind.&#039;&#039; Because the simple task&#039;s code is a function in our program, we must stop the subtask before exiting.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// simpletask.c - Uses the function CreateTaskTags() to create a simple subtask.&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/tasks.h&amp;gt;&lt;br /&gt;
#include &amp;lt;libraries/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define STACK_SIZE 16000&lt;br /&gt;
&lt;br /&gt;
uint32 sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
/* our function prototypes */&lt;br /&gt;
void simpletask(void);&lt;br /&gt;
void cleanexit(CONST_STRPTR, int32);&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
    struct Task *task = CreateTaskTags(&amp;quot;SimpleTask&amp;quot;, 0 , simpletask, STACK_SIZE,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if(task == NULL)  cleanexit(&amp;quot;Can&#039;t create task&amp;quot;, RETURN_FAIL);&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;This program initialized a variable to zero, then started a\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;separate task which is incrementing that variable right now,\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;while this program waits for you to press RETURN.\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Press RETURN now: &amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;FGetC( IDOS-&amp;gt;Input() );&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;The shared variable now equals %ld\n&amp;quot;, sharedvar);&lt;br /&gt;
&lt;br /&gt;
    /* We can simply remove the task we added because our simpletask does not make */&lt;br /&gt;
    /* any system calls which could cause it to be awakened or signalled later.    */&lt;br /&gt;
    IExec-&amp;gt;Forbid();&lt;br /&gt;
    IEXec-&amp;gt;DeleteTask(task);&lt;br /&gt;
    IExec-&amp;gt;Permit();&lt;br /&gt;
    cleanexit(&amp;quot;&amp;quot;, RETURN_OK);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void simpletask(void)&lt;br /&gt;
{&lt;br /&gt;
    while(sharedvar &amp;lt; 0x8000000) sharedvar++;&lt;br /&gt;
    /* Wait forever because main() is going to RemTask() us */&lt;br /&gt;
    IExec-&amp;gt;Wait(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void cleanexit(CONST_STRPTR s, int32 e)&lt;br /&gt;
{&lt;br /&gt;
    if(s != NULL) IDOS-&amp;gt;Printf(&amp;quot;%s\n&amp;quot;, s);&lt;br /&gt;
    exit(e);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Stack ===&lt;br /&gt;
&lt;br /&gt;
Every task requires a stack. All normal code execution occurs on this task stack. Special modes of execution (processor traps and system interrupts for example) execute on a single &#039;&#039;supervisor mode&#039;&#039; stack and do not directly affect task stacks.&lt;br /&gt;
&lt;br /&gt;
Task stacks are normally used to store local variables, subroutine return addresses, and saved register values. Additionally, when a task loses the processor, all of its current registers are preserved on this stack (with the exception of the stack pointer itself, which must be saved in the task structure).&lt;br /&gt;
&lt;br /&gt;
The amount of stack used by a task can vary widely. As a general rule of thumb, a minimum stack size of 16000 bytes should be used. If any GUI elements are involved, a minimum stack size of 80000 bytes is recommended.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Because stack-bounds checking is not provided as a service of Exec, it is important to provide enough space for your task stack. Stack overflows are always difficult to debug and may result not only in the erratic failure of your task but also in the mysterious malfunction of other Amiga subsystems.&#039;&#039;&#039; Some compilers may provide a stack-checking option.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;You Can&#039;t Always Check The Stack.&#039;&#039; Such stack-checking options generally cannot be used if part of your code will be running on the system stack (interrupts, exceptions, handlers, servers) or on a different task&#039;s stack (libraries, devices, created tasks).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When choosing your stack size, do not cut it too close. Remember that any recursive routines in your code may use varying amounts of stack, and that future versions of system routines may use additional stack variables. By dynamically allocating buffers and arrays, most application programs can be designed to function comfortably within the recommended minimum process stack size of 16000 bytes.&lt;br /&gt;
&lt;br /&gt;
=== Task Priority ===&lt;br /&gt;
&lt;br /&gt;
A task&#039;s priority indicates its importance relative to other tasks. Higher-priority tasks receive the processor before lower-priority tasks do. Task priority is stored as a signed number ranging from -128 to +127. Higher priorities are represented by more positive values; zero is considered the neutral priority. Normally, system tasks execute somewhere in the range of +20 to -20, and most application tasks execute at priority 0.&lt;br /&gt;
&lt;br /&gt;
It is not wise to needlessly raise a task&#039;s priority. Sometimes it may be necessary to carefully select a priority so that the task can properly interact with various system tasks. The SetTaskPri() Exec function is provided for this purpose.&lt;br /&gt;
&lt;br /&gt;
== Task Termination ==&lt;br /&gt;
&lt;br /&gt;
Task termination may occur as the result of a number of situations:&lt;br /&gt;
&lt;br /&gt;
* A program returning from its initialPC routine and dropping into its finalPC routine or the system default finalizer.&lt;br /&gt;
* A task trap that is too serious for a recovery action. This includes traps like processor bus error, odd address access errors, etc.&lt;br /&gt;
* A trap that is not handled by the task. For example, the task might be terminated if your code happened to encounter a processor TRAP instruction and you did not provide a trap handling routine.&lt;br /&gt;
* An explicit call to RemTask() or DeleteTask().&lt;br /&gt;
&lt;br /&gt;
Task termination involves the deallocation of system resources and the removal of the task structure from Exec. The most important part of task termination is the deallocation of system resources. A task must return all memory that it allocated for its private use, it must terminate any outstanding I/O commands, and it must close access to any system libraries or devices that it has opened.&lt;br /&gt;
&lt;br /&gt;
It is wise to adopt a strategy for task clean-up responsibility. You should decide whether resource allocation and deallocation is the duty of the creator task or the newly created task. Often it is easier and safer for the creator to handle the resource allocation and deallocation on behalf of its offspring. In such cases, before removing the child task, you must make sure it is in a safe state such as Wait(0L) and not still using a resources or waiting for an event or signal that might still occur.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitabl&amp;quot;&lt;br /&gt;
| &#039;&#039;NOTE:&#039;&#039; Certain resources, such as signals and created ports, must be allocated and deallocated by the same task that will wait on them. Also note that if your subtask code is part of your loaded program, you must not allow your program to exit before its subtasks have cleaned up their allocations, and have been either deleted or placed in a safe state such as Wait(0).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Exclusion ==&lt;br /&gt;
&lt;br /&gt;
From time to time the advanced system program may find it necessary to access global system data structures. Because these structures are shared by the system and by other tasks that execute asynchronously to your task, a task must prevent other tasks from using these structures while it is reading from or writing to them. Task exclusion methods described below prevent the operating system from switching tasks by &#039;&#039;forbidding&#039;&#039; or &#039;&#039;disabling&#039;&#039;. A section of code that requires the use of either of these mechanisms to lock out access by others is termed a &#039;&#039;critical section&#039;&#039;. &#039;&#039;&#039;Use of these methods is discouraged. For arbitrating access to data between your tasks, &#039;&#039;mutexes&#039;&#039; are a superior solution (See [[Exec_Mutexes|Exec Mutexes]]). The below solution is deprecated and will be removed from the AmigaOS soon.&lt;br /&gt;
&lt;br /&gt;
=== Forbidding Task Switching ===&lt;br /&gt;
&lt;br /&gt;
Forbidding is used when a task is accessing shared structures that might also be accessed at the same time from another task. It effectively eliminates the possibility of simultaneous access by imposing &#039;&#039;non-preemptive&#039;&#039; task scheduling. &#039;&#039;&#039;This has the net effect of disabling multitasking for as long as your task remains in its running state.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
While forbidden, your task will continue running until it performs a call to Wait() or exits from the forbidden state. Interrupts will occur normally, but no new tasks will be dispatched, &#039;&#039;regardless of their priorities&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a task running in the forbidden state calls the Wait() function, directly or indirectly, it implies a temporary exit from its forbidden state. Since almost all stdio, device I/O, and file I/O functions must Wait() for I/O completion, performing such calls will cause your task to Wait(), temporarily breaking the forbid. While the task is waiting, the system will perform normally. When the task receives one of the signals it is waiting for, it will again reenter the forbidden state.&lt;br /&gt;
&lt;br /&gt;
To become forbidden, a task calls the Forbid() function. To escape, the Permit() function is used. The use of these functions may be nested with the expected affects; you will not exit the forbidden mode until you call the outermost Permit().&lt;br /&gt;
&lt;br /&gt;
As an example, the Exec task list should only be accessed when in a Forbid() state. Accessing the list without forbidding could lead to incorrect results or it could crash the entire system. To access the task list also requires the program to disable interrupts which is discussed in the next section.&lt;br /&gt;
&lt;br /&gt;
=== Disabling Tasks ===&lt;br /&gt;
&lt;br /&gt;
Disabling is similar to forbidding, but it also prevents interrupts from occurring during a critical section. Disabling is required when a task accesses structures that are shared by interrupt code. It eliminates the possibility of an interrupt accessing shared structures by preventing interrupts from occurring. &#039;&#039;&#039;Use of disabling is strongly discouraged.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To disable interrupts you can call the Disable() function. To enable interrupts again, use the Enable() function.&lt;br /&gt;
&lt;br /&gt;
Like forbidden sections, disabled sections can be nested. To restore normal interrupt processing, an Enable() call must be made for every Disable(). Also like forbidden sections, any direct or indirect call to the Wait() function will enable interrupts until the task regains the processor.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;WARNING: It is important to realize that there is a danger in using disabled sections&#039;&#039;. Because the software on the Amiga depends heavily on its interrupts occurring in nearly real time, you cannot disable for more than a very brief instant. Disabling interrupts for more than 250 microseconds can interfere with the normal operation of vital system functions, especially serial I/O.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It is never necessary to both Disable() and Forbid(). Because disabling prevents interrupts, it also prevents preemptive task scheduling. When disable is used within an interrupt, it will have the effect of locking out all higher level interrupts (lower level interrupts are automatically disabled by the CPU). Many Exec lists can only be accessed while disabled. Suppose you want to print the names of all system tasks. You would need to access both the TaskReady and TaskWait lists from within a single disabled section. In addition, you must avoid calling system functions that would break a disable by an indirect call to Wait() (Printf() for example). In this example, the names are gathered into a list while task switching is disabled. Then task switching is enabled and the names are printed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// tasklist.c - Snapshots and prints the ExecBase task list&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/lists.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/nodes.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/execbase.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/utility.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CONST_STRPTR VersTag = &amp;quot;$VER: tasklist 53.1 (21.6.2012)&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
extern struct ExecBase *SysBase;&lt;br /&gt;
&lt;br /&gt;
/* Use extended structure to hold task information */&lt;br /&gt;
struct TaskNode {&lt;br /&gt;
    struct Node tn_Node;&lt;br /&gt;
    uint32 tn_TaskAddress;&lt;br /&gt;
    uint32 tn_SigAlloc;&lt;br /&gt;
    uint32 tn_SigWait;&lt;br /&gt;
    TEXT tn_Name[32];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    struct TaskNode *tnode = NULL;&lt;br /&gt;
    struct TaskNode *rnode = NULL;&lt;br /&gt;
&lt;br /&gt;
    /* Allocate memory for our list */&lt;br /&gt;
    struct List *ourtasklist = IExec-&amp;gt;AllocSysObjectTags(ASOT_LIST, TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ourtasklist != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        /* Make sure tasks won&#039;t switch lists or go away */&lt;br /&gt;
        IExec-&amp;gt;Disable();&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task WAIT list */&lt;br /&gt;
        struct List *exectasklist = &amp;amp;(SysBase-&amp;gt;TaskWait);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
&lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
            }&lt;br /&gt;
            else break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task READY list */&lt;br /&gt;
        exectasklist = &amp;amp;(SysBase-&amp;gt;TaskReady);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
        &lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizoeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
                if(!rnode)  rnode = tnode;  /* first READY task */&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Re-enable interrupts and taskswitching */&lt;br /&gt;
        IExec-&amp;gt;Enable();&lt;br /&gt;
&lt;br /&gt;
        /* Print now (printing above would have defeated a Forbid or Disable) */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Pri Address     SigAlloc    SigWait    Taskname\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        struct TaskNode *node = (struct TaskNode *)IExec-&amp;gt;GetHead(ourtasklist);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nWAITING:\n&amp;quot;);&lt;br /&gt;
        while (tnode = (struct TaskNode *)IExec-&amp;gt;GetSucc((struct Node*)node))&lt;br /&gt;
        {&lt;br /&gt;
            if(tnode == rnode)&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;\nREADY:\n&amp;quot;);  /* we set rnode above */&lt;br /&gt;
                &lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                    node-&amp;gt;tn_Node.ln_Pri, node-&amp;gt;tn_TaskAddress, node-&amp;gt;tn_SigAlloc,&lt;br /&gt;
                    node-&amp;gt;tn_SigWait, node-&amp;gt;tn_Name);&lt;br /&gt;
&lt;br /&gt;
            /* Free the memory, no need to remove the node, referenced once only */&lt;br /&gt;
            IExec-&amp;gt;FreeVec(node);&lt;br /&gt;
            node = tnode;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_LIST, ourtasklist);&lt;br /&gt;
&lt;br /&gt;
        /* Say who we are */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nTHIS TASK:\n&amp;quot;);&lt;br /&gt;
        struct Task *task = FindTask(NULL);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                task-&amp;gt;tc_Node.ln_Pri, task, task-&amp;gt;tc_SigAlloc,&lt;br /&gt;
                task-&amp;gt;tc_SigWait, task-&amp;gt;tc_Node.ln_Name);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Mutexes and Semaphores ===&lt;br /&gt;
&lt;br /&gt;
Mutexes and semaphores can be used for the purposes of mutual exclusion. With this method of locking, all tasks agree on a locking convention before accessing shared data structures. Tasks that do not require access are not affected and will run normally, so this type of exclusion is considered preferable to forbidding and disabling. This form of exclusion is explained in more detail in [[Exec_Mutexes|Mutexes]] and [[Exec_Semaphores|Semaphores]].&lt;br /&gt;
&lt;br /&gt;
== Task Exceptions ==&lt;br /&gt;
&lt;br /&gt;
Exec can provide a task with its own task-local &amp;quot;interrupt&amp;quot; called an &#039;&#039;exception&#039;&#039;. When some exceptional event occurs, an Exec exception occurs which stops a particular task from executing its normal code and forces it to execute a special, task-specific exception handling routine.&lt;br /&gt;
&lt;br /&gt;
To set up an exception routine for a task requires setting values in the task&#039;s control structure (the Task structure). The tc_ExceptCode field should point to the task&#039;s exception handling routine. If this field is zero, Exec will ignore all exceptions. The tc_ExceptData field should point to any data the exception routine needs.&lt;br /&gt;
&lt;br /&gt;
Exec exceptions work using signals. When a specific signal or signals occur, Exec will stop a task and execute its exception routine. Use the Exec function SetExcept() to tell Exec which of the task&#039;s signals should trigger the exception.&lt;br /&gt;
&lt;br /&gt;
When an exception occurs, Exec stops executing the tasks normal code and jumps immediately into the exception routine, no matter what the task was doing. The exception routine operates in the same context the task&#039;s normal code; it operates in the CPU&#039;s user mode and uses the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
Before entering the exception routine, Exec pushes the normal task code&#039;s context onto the stack. Exec then puts certain parameters in the processor registers for the exception routine to use. D0 contains a signal mask indicating which signal bit or bits caused the exception. Exec disables these signals when the task enters its exception routine. If more than one signal bit is set (i.e. if two signals occurred simultaneously), it is up to the exception routine to decide in what order to process the two different signals. A1 points to the related exception data (from tc_ExceptData), and A6 contains the Exec library base. You can think of an exception as a subtask outside of your normal task. Because task exception code executes in &#039;&#039;user&#039;&#039; mode, however, the task stack must be large enough to supply the extra space consumed during an exception.&lt;br /&gt;
&lt;br /&gt;
While processing a given exception, Exec prevents that exception from occurring recursively. At exit from your exception-processing code, you should make sure D0 contains the signal mask the exception routine received in D0 because Exec looks here to see which signals it should reactivate. When the task executes the RTS instruction at the end of the exception routine, the system restores the previous contents of all of the task registers and resumes the task at the point where it was interrupted by the exception signal.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Exceptions Are Tricky&#039;&#039;. Exceptions are difficult to use safely. An exception can interrupt a task that is executing a critical section of code within a system function, or one that has locked a system resource such as the disk or blitter (note that even simple text output uses the blitter.) This possibility makes it dangerous to use most system functions within an exception unless you are sure that your interrupted task was performing only local, non-critical operations.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Traps ==&lt;br /&gt;
&lt;br /&gt;
Task &#039;&#039;traps&#039;&#039; are synchronous exceptions to the normal flow of program control. They are always generated as a direct result of an operation performed by your program&#039;s code. Whether they are accidental or purposely generated, they will result in your program being forced into a special condition in which it must immediately handle the trap. Address error, privilege violation, zero divide, and trap instructions all result in task traps. They may be generated directly by the &#039;&#039;68000&#039;&#039; processor (Motorola calls them &amp;quot;exceptions&amp;quot;) or simulated by software.&lt;br /&gt;
&lt;br /&gt;
A task that incurs a trap has no choice but to respond immediately. The task must have a module of code to handle the trap. Your task may be aborted if a trap occurs and no means of handling it has been provided. Default trap handling code (tc_TrapCode) is provided by the OS. You may instead choose to do your own processing of traps. The tc_TrapCode field is the address of the handler that you have designed to process the trap. The tc_TrapData field is the address of the data area for use by the trap handler.&lt;br /&gt;
&lt;br /&gt;
The system&#039;s default trap handling code generally displays a Software Error Requester or Alert containing an exception number and the program counter or task address. Processor exceptions generally have numbers in the range hex 00 to 2F. The &#039;&#039;68000&#039;&#039; processor exceptions of particular interest are as follows.&lt;br /&gt;
&lt;br /&gt;
Traps (68000 Exception Vector Numbers)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 2 || Bus error || access of nonexistent memory&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Address error || long/word access of odd address (&#039;&#039;68000&#039;&#039;)&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Illegal instruction || illegal opcode (other than Axxx or Fxxx)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Zero divide || processor division by zero&lt;br /&gt;
|-&lt;br /&gt;
| 6 || CHK instruction || register bounds error trap by CHK&lt;br /&gt;
|-&lt;br /&gt;
| 7 || TRAPV instruction || overflow error trap by TRAPV&lt;br /&gt;
|-&lt;br /&gt;
| 8 || Privilege violation || user execution of supervisor opcode&lt;br /&gt;
|-&lt;br /&gt;
| 9 || Trace || status register TRACE bit trap&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Line 1010 emulator || execution of opcode beginning with $A&lt;br /&gt;
|-&lt;br /&gt;
| 11 || Line 1111 emulator || execution of opcode beginning with $F&lt;br /&gt;
|-&lt;br /&gt;
| 32-47 || Trap instructions || TRAP &#039;&#039;N&#039;&#039; instruction where &#039;&#039;N&#039;&#039; = 0 to 15&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A system alert for a processor exception may set the high bit of the longword exception number to indicate an unrecoverable error (for example $8000¬†0005 for an unrecoverable processor exception #5). System alerts with more complex numbers are generally Amiga-specific software failures. These are built from the definitions in the &amp;amp;lt;exec/alerts.h&amp;amp;gt; include file.&lt;br /&gt;
&lt;br /&gt;
The actual stack frames generated for these traps are processor-dependent. The &#039;&#039;68010&#039;&#039;, &#039;&#039;68020&#039;&#039;, and &#039;&#039;68030&#039;&#039; processors will generate a different type of stack frame than the &#039;&#039;68000&#039;&#039;. If you plan on having your program handle its own traps, you should not make assumptions about the format of the supervisor stack frame. Check the flags in the AttnFlags field of the ExecBase structure for the type of processor in use and process the stack frame accordingly.&lt;br /&gt;
&lt;br /&gt;
=== Trap Handlers ===&lt;br /&gt;
&lt;br /&gt;
For compatibility with the &#039;&#039;68000&#039;&#039;, Exec performs trap handling in supervisor mode. This means that all task switching is disabled during trap handling.&lt;br /&gt;
&lt;br /&gt;
At entry to the task&#039;s trap handler, the system stack contains a processor-dependent trap frame as defined in the &#039;&#039;68000&#039;&#039;/&#039;&#039;10&#039;&#039;/&#039;&#039;20&#039;&#039;/&#039;&#039;30&#039;&#039; manuals. A longword exception number is added to this frame. That is, when a handler gains control, the top of stack contains the exception number and the trap frame immediately follows.&lt;br /&gt;
&lt;br /&gt;
To return from trap processing, remove the exception number from the stack (note that this is the supervisor stack, not the user stack) and then perform a return from exception (RTE).&lt;br /&gt;
&lt;br /&gt;
Because trap processing takes place in supervisor mode, with task dispatching disabled, it is strongly urged that you keep trap processing as short as possible or switch back to user mode from within your trap handler. If a trap handler already exists when you add your own trap handler, it is smart to propagate any traps that you do not handle down to the previous handler. This can be done by saving the previous address from tc_TrapCode and having your handler pass control to that address if the trap which occurred is not one you wish to handle.&lt;br /&gt;
&lt;br /&gt;
The following example installs a simple trap handler which intercepts processor divide-by-zero traps, and passes on all other traps to the previous default trap code. The example has two code modules which are linked together. The trap handler code is in assembler. The C module installs the handler, demonstrates its effectiveness, then restores the previous tc_TrapCode.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* trap_c.c - Execute me to compile me with SAS C 5.10&lt;br /&gt;
LC -b0 -cfistq -v -y -j73 trap_c.c&lt;br /&gt;
Blink FROM LIB:c.o,trap_c.o,trap_a.o TO trap LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit&lt;br /&gt;
&lt;br /&gt;
trap_c.c - C module of sample integer divide-by-zero trap&lt;br /&gt;
*/&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;exec/tasks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdlib.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdio.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE&lt;br /&gt;
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */&lt;br /&gt;
int chkabort(void) {return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
extern ULONG trapa();           /* assembler trap code in trap_a.asm */&lt;br /&gt;
&lt;br /&gt;
APTR oldTrapCode;&lt;br /&gt;
ULONG countdiv0;&lt;br /&gt;
&lt;br /&gt;
void main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
    struct Task *thistask;&lt;br /&gt;
    ULONG k,j;&lt;br /&gt;
&lt;br /&gt;
    thistask = FindTask(NULL);&lt;br /&gt;
&lt;br /&gt;
    /* Save our task&#039;s current trap code pointer */&lt;br /&gt;
    oldTrapCode = thistask-&amp;amp;gt;tc_TrapCode;&lt;br /&gt;
&lt;br /&gt;
    /* Point task to our assembler trap handler code.  Ours will just count */&lt;br /&gt;
    /* divide-by-zero traps, and pass other traps on to the normal TrapCode */&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = (APTR)trapa;&lt;br /&gt;
&lt;br /&gt;
    countdiv0 = 0L;&lt;br /&gt;
&lt;br /&gt;
    for(k=0; k&amp;amp;lt;4; k++)            /* Let&#039;s divide by zero a few times */&lt;br /&gt;
       {&lt;br /&gt;
       printf(&amp;amp;quot;dividing %ld by zero... &amp;amp;quot;,k);&lt;br /&gt;
       j = k/0L;&lt;br /&gt;
       printf(&amp;amp;quot;did it\n&amp;amp;quot;);&lt;br /&gt;
       }&lt;br /&gt;
    printf(&amp;amp;quot;\nDivide by zero happened %ld times\n&amp;amp;quot;,countdiv0);&lt;br /&gt;
&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = oldTrapCode;     /* Restore old trap code */&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;* trap_a.asm - Example trap handling code (leaves D0 intact).  Entered&lt;br /&gt;
* in supervisor mode with the following on the supervisor stack:&lt;br /&gt;
*    0(sp).l = trap#&lt;br /&gt;
*    4(sp) Processor dependent exception frame&lt;br /&gt;
&lt;br /&gt;
    INCLUDE &amp;amp;quot;exec/types.i&amp;amp;quot;&lt;br /&gt;
    INCLUDE &amp;amp;quot;libraries/dos.i&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
        XDEF _trapa&lt;br /&gt;
        XREF _countdiv0&lt;br /&gt;
        XREF _oldTrapCode&lt;br /&gt;
&lt;br /&gt;
        CODE&lt;br /&gt;
_trapa:                                 ; our trap handler entry&lt;br /&gt;
        CMPI.L  #5,(SP)                 ; is this a divide by zero ?&lt;br /&gt;
        BNE.S   notdiv0                 ; no&lt;br /&gt;
        ADD.L   #1,_countdiv0           ; yes, increment our div0 count&lt;br /&gt;
endtrap:&lt;br /&gt;
        ADDQ    #4,SP                   ; remove exception number from SSP&lt;br /&gt;
        RTE                             ; return from exception&lt;br /&gt;
notdiv0:&lt;br /&gt;
        TST.L   _oldTrapCode            ; is there another trap handler ?&lt;br /&gt;
        BEQ.S   endtrap                 ; no, so we&#039;ll exit&lt;br /&gt;
        MOVE.L  _oldTrapCode,-(SP)      ; yes, go on to old TrapCode&lt;br /&gt;
        RTS                             ; jumps to old TrapCode&lt;br /&gt;
&lt;br /&gt;
        END&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Trap Instructions ===&lt;br /&gt;
&lt;br /&gt;
The TRAP instructions in the &#039;&#039;68000&#039;&#039; generate traps 32-47. Because many independent pieces of system code may desire to use these traps, the AllocTrap() and FreeTrap() functions are provided. These work in a fashion similar to that used by AllocSignal() and FreeSignal(), mentioned above.&lt;br /&gt;
&lt;br /&gt;
Allocating a trap is simply a bookkeeping job within a task. It does not affect how the system calls the trap handler; it helps coordinate who owns what traps. Exec does nothing to determine whether or not a task is prepared to handle a particular trap. It simply calls your code. It is up to your program to handle the trap.&lt;br /&gt;
&lt;br /&gt;
To allocate any trap, you can use the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(-1)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;all trap instructions are in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can select a specific trap using this code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(3)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;trap #3 is in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To free a trap, you use the FreeTrap() function passing it the trap number to be freed.&lt;br /&gt;
&lt;br /&gt;
== Processor and Cache Control ==&lt;br /&gt;
&lt;br /&gt;
Exec provides a number of to control the processor mode and, if available, the caches. All these functions work independently of the specific &#039;&#039;M68000&#039;&#039; family processor type. This enables you to write code which correctly controls the state of both the &#039;&#039;MC68000&#039;&#039; and the &#039;&#039;MC68040&#039;&#039;. Along with processor mode and cache control, functions are provided to obtain information about the condition code register (CCR) and status register (SR). No functions are provided to control a paged memory management unit (PMMU) or floating point unit (FPU).&lt;br /&gt;
&lt;br /&gt;
Processor and Cache Control Functions&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| GetCC() || Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR() || Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState() || Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor() || Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState() || Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE() || Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU() || Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl() || Global cache control.&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA() || Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA() || Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Supervisor Mode ===&lt;br /&gt;
&lt;br /&gt;
While in supervisor mode, you have complete access to all data and registers, including those used for task scheduling and exceptions, and can execute privileged instructions. In application programs, normally only task trap code is directly executed in supervisor mode, to be compatible with the &#039;&#039;MC68000&#039;&#039;. For normal applications, it should never be necessary to switch to supervisor mode itself, only indirectly through Exec function calls. Remember that task switching is disabled while in supervisor mode. If it is absolutely needed to execute code in supervisor mode, keep it as brief as possible.&lt;br /&gt;
&lt;br /&gt;
Supervisor mode can only be entered when a &#039;&#039;680x0&#039;&#039; exception occurs (an interrupt or trap). The Supervisor() function allows you to trap an exception to a specified assembly function. In this function your have full access to all registers. No registers are saved when your function is invoked. You are responsible for restoring the system to a sane state when you are done. You must return to user mode with an RTE instruction. You must not return to user mode by executing a privileged instruction which clears the supervisor bit in the status register. Refer to a manual on the &#039;&#039;M68000&#039;&#039; family of CPUs for information about supervisor mode and available privileged instructions per processor type.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;MC68000&#039;&#039; has two stacks, the user stack (USP) and supervisor stack (SSP). As of the &#039;&#039;MC68020&#039;&#039; there are two supervisor stacks, the interrupt stack pointer (ISP) and the master stack pointer (MSP). The SuperState() function allows you to enter supervisor mode with the USP used as SSP. The function returns the SSP, which will be the MSP, if an &#039;&#039;MC68020&#039;&#039; or greater is used. Returning to user mode is done with the UserState() function. This function takes the SSP as argument, which must be saved when SuperState() is called. Because of possible problems with stack size, Supervisor() is to be preferred over SuperState().&lt;br /&gt;
&lt;br /&gt;
=== Status Register ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The processor status register bits can be set or read with the SetSR() function. This function operates in supervisor mode, thus both the upper and lower byte of the SR can be read or set. Be very sure you know what you are doing when you use this function to set bits in the SR and above all never try to use this function to enter supervisor mode. Refer to the &#039;&#039;M68000 Programmers Reference Manual&#039;&#039; by Motorola Inc. for information about the definition of individual SR bits per processor type.&lt;br /&gt;
&lt;br /&gt;
=== Condition Code Register ===&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;MC68000&#039;&#039; a copy of the processor condition codes can be obtained with the MOVE SR,&amp;amp;lt;ea&amp;amp;gt; instruction. On &#039;&#039;MC68010&#039;&#039; processors and up however, the instruction MOVE CCR,&amp;amp;lt;ea&amp;amp;gt; must be used. Using the specific &#039;&#039;MC68000&#039;&#039; instruction on later processors will cause a &#039;&#039;680x0&#039;&#039; exception since it is a privileged instruction on those processors. The GetCC() function provides a processor independent way of obtaining a copy of the condition codes. For all processors there are 5 bits which can indicate the result of an integer or a system control instruction:&lt;br /&gt;
&lt;br /&gt;
* X - extend&lt;br /&gt;
* N - negative&lt;br /&gt;
* Z - zero&lt;br /&gt;
* V - overflow&lt;br /&gt;
* C - carry&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;X&#039;&#039;&#039; bit is used for multiprecision calculations. If used, it is copy of the carry bit. The other bits state the result of a processor operation.&lt;br /&gt;
&lt;br /&gt;
=== Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
As of the &#039;&#039;MC68020&#039;&#039; all processors have an instruction cache, 256 bytes on the &#039;&#039;MC68020&#039;&#039; and &#039;&#039;MC68030&#039;&#039; and 4 KBytes on a &#039;&#039;MC68040&#039;&#039;. The &#039;&#039;MC68030&#039;&#039; and &#039;&#039;MC68040&#039;&#039; have data caches as well, 256 bytes and 4 KBytes respectively. All the processors load instructions ahead of the program counter (PC), albeit it that the &#039;&#039;MC68000&#039;&#039; and &#039;&#039;MC68010&#039;&#039; only prefetch one and two words respectively. This means the CPU loads instructions ahead of the current program counter. For this reason self-modifying code is strongly discouraged. If your code modifies or decrypts itself just ahead of the program counter, the pre-fetched instructions may not match the modified instructions. If self-modifying code must be used, flushing the cache is the safest way to prevent this.&lt;br /&gt;
&lt;br /&gt;
=== DMA Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
The CachePreDMA() and CachePostDMA() functions allow you to flush the data cache before and after Direct Memory Access. Typically only DMA device drivers benefit from this. These functions take the processor type, possible MMU and cache mode into account. When no cache is available they end up doing nothing. These functions can be replaced with ones suitable for different cache hardware. Refer to the SDK for implementation specifics.&lt;br /&gt;
&lt;br /&gt;
Since DMA device drivers read and write directly to memory, they are effected by the CopyBack feature of the &#039;&#039;MC68040&#039;&#039; (explained below). Using DMA with CopyBack mode requires a cache flush. If a DMA device needs to read RAM via DMA, it must make sure that the data in the caches has been written to memory first, by calling CachePreDMA(). In case of a write to memory, the DMA device should first clear the caches with CachePreDMA(), write the data and flush the caches again with CachePostDMA().&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following chart gives a brief description of the Exec functions that control tasks. See the SDK for details about each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Exec Task Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AddTask()&lt;br /&gt;
| Add a task to the system.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTrap()&lt;br /&gt;
| Allocate a processor trap vector.&lt;br /&gt;
|-&lt;br /&gt;
| Disable()&lt;br /&gt;
| Disable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| Enable()&lt;br /&gt;
| Enable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| FindTask()&lt;br /&gt;
| Find a specific task.&lt;br /&gt;
|-&lt;br /&gt;
| Forbid()&lt;br /&gt;
| Forbid task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| FreeTrap()&lt;br /&gt;
| Release a process trap.&lt;br /&gt;
|-&lt;br /&gt;
| Permit()&lt;br /&gt;
| Permit task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| SetTaskPri()&lt;br /&gt;
| Set the priority of a task.&lt;br /&gt;
|-&lt;br /&gt;
| RemTask()&lt;br /&gt;
| Remove a task from the system.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE()&lt;br /&gt;
| Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU()&lt;br /&gt;
| Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl()&lt;br /&gt;
| Global cache control (V37).&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA()&lt;br /&gt;
| Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA()&lt;br /&gt;
| Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| GetCC()&lt;br /&gt;
| Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR()&lt;br /&gt;
| Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState()&lt;br /&gt;
| Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor()&lt;br /&gt;
| Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState()&lt;br /&gt;
| Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CreateTask()&lt;br /&gt;
| Setup and add a new task.&lt;br /&gt;
|-&lt;br /&gt;
| DeleteTask()&lt;br /&gt;
| Delete a task created with CreateTask().&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3420</id>
		<title>Exec Tasks</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3420"/>
		<updated>2012-07-10T17:51:39Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Task Exclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{NeedUpdate}}&lt;br /&gt;
== Exec Tasks ==&lt;br /&gt;
&lt;br /&gt;
One of the most powerful features of the Amiga operating system is its ability to run and manage multiple independent program tasks, providing each task with processor time based on their priority and activity. These tasks include system device drivers, background utilities, and user interface environments, as well as normal application programs. This multitasking capability is provided by the Exec library&#039;s management of task creation, termination, scheduling, event signals, traps, exceptions, and mutual exclusion.&lt;br /&gt;
&lt;br /&gt;
This section deals with Exec on a lower level than most applications programmers need and assumes you are already familiar with the Exec basics discussed in the [[Introduction_to_Exec|Introduction to Exec]].&lt;br /&gt;
&lt;br /&gt;
== Task Structure ==&lt;br /&gt;
&lt;br /&gt;
Exec maintains task context and state information in a task-control data structure. Like most Exec structures, Task structures are dynamically linked onto various task queues through the use of an embedded Exec list Node structure (see the Lists chapter). Any task can find its own task structure by calling FindTask(NULL). The C-language form of this structure is defined in the &amp;amp;lt;exec/tasks.h&amp;amp;gt; include file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Task  {&lt;br /&gt;
    struct Node tc_Node;&lt;br /&gt;
    UBYTE       tc_Flags;&lt;br /&gt;
    UBYTE       tc_State;&lt;br /&gt;
    BYTE        tc_IDNestCnt;   /* intr disabled nesting */&lt;br /&gt;
    BYTE        tc_TDNestCnt;   /* task disabled nesting */&lt;br /&gt;
    ULONG       tc_SigAlloc;    /* sigs allocated */&lt;br /&gt;
    ULONG       tc_SigWait;     /* sigs we are waiting for */&lt;br /&gt;
    ULONG       tc_SigRecvd;    /* sigs we have received */&lt;br /&gt;
    ULONG       tc_SigExcept;   /* sigs we will take excepts for */&lt;br /&gt;
    UWORD       tc_TrapAlloc;   /* traps allocated */&lt;br /&gt;
    UWORD       tc_TrapAble;    /* traps enabled */&lt;br /&gt;
    APTR        tc_ExceptData;  /* points to except data */&lt;br /&gt;
    APTR        tc_ExceptCode;  /* points to except code */&lt;br /&gt;
    APTR        tc_TrapData;    /* points to trap code */&lt;br /&gt;
    APTR        tc_TrapCode;    /* points to trap data */&lt;br /&gt;
    APTR        tc_SPReg;       /* stack pointer */&lt;br /&gt;
    APTR        tc_SPLower;     /* stack lower bound */&lt;br /&gt;
    APTR        tc_SPUpper;     /* stack upper bound + 2*/&lt;br /&gt;
    VOID      (*tc_Switch)();   /* task losing CPU */&lt;br /&gt;
    VOID      (*tc_Launch)();   /* task getting CPU */&lt;br /&gt;
    struct List tc_MemEntry;    /* allocated memory */&lt;br /&gt;
    APTR        tc_UserData;    /* per task data */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Most of these fields are not relevant for simple tasks; they are used by Exec for state and administrative purposes. A few fields, however, are provided for the advanced programs that support higher level environments (as in the case of &#039;&#039;processes&#039;&#039;) or require precise control (as in &#039;&#039;devices&#039;&#039;). The following sections explain these fields in more detail.&lt;br /&gt;
&lt;br /&gt;
== Task Creation ==&lt;br /&gt;
&lt;br /&gt;
To create a new task manually you must allocate a task structure, initialize its various fields, and then link it into Exec with a call to AddTask(). This is not the recommended way to create a task. This detailed information is only being provided for reference.&lt;br /&gt;
&lt;br /&gt;
The task structure may be allocated by calling the AllocVecTags() function with the MEMF_SHARED attribute and AVT_ClearWithValue tag.&lt;br /&gt;
&lt;br /&gt;
The Task fields that require initialization depend on how you intend to use the task. For the simplest of tasks, only a few fields must be initialized:&lt;br /&gt;
&lt;br /&gt;
; tc_Node&lt;br /&gt;
: The task list node structure. This includes the task&#039;s priority, its type, and its name (refer to [[Exec_Lists_and_Queues|Exec Lists and Queues]] chapter).&lt;br /&gt;
&lt;br /&gt;
; tc_SPLower&lt;br /&gt;
: The lower memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPUpper&lt;br /&gt;
: The upper memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPReg&lt;br /&gt;
: The initial stack pointer. Because task stacks grow &#039;&#039;downward&#039;&#039; in memory, this field is usually set to the same value as tc_SPUpper.&lt;br /&gt;
&lt;br /&gt;
Zeroing all other unused fields will cause Exec to supply the appropriate system default values. Allocating the structure with the AVT_ClearWithValue tag is an easy way to be sure that this happens.&lt;br /&gt;
&lt;br /&gt;
Once the structure has been initialized, it must be linked to Exec. This is done with a call to AddTask() in which the following parameters are specified:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
AddTask(struct Task *task, APTR initialPC, APTR finalPC )&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The task argument is a pointer to your initialized Task structure. Set initialPC to the entry point of your task code. This is the address of the first instruction the new task will execute.&lt;br /&gt;
&lt;br /&gt;
Set finalPC to the address of the finalization code for your task. This is a code section that will receive control if the initialPC routine ever performs a return. This exists to prevent your task from being launched into random memory upon an accidental return. The finalPC routine should usually perform various program-related clean-up duties and should then remove the task. If a zero is supplied for this parameter, Exec will use its default finalization code (which simply calls the RemTask() function).&lt;br /&gt;
&lt;br /&gt;
=== Task Creation With CreateTask() ===&lt;br /&gt;
&lt;br /&gt;
The recommended of creating a task is with CreateTask() or CreateTaskTags() functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
CreateTaskTags(CONST_STRPTR name, int32 priority, APTR initialPC, uint32 stacksize, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A task created with CreateTaskTags() may be removed with the DeleteTask() function, or it may simply return when it is finished. CreateTaskTags() adds a MemList to the tc_MemEntry of the task it creates, describing all memory it has allocated for the task, including the task stack and the Task structure itself. This memory will be deallocated by Exec when the task is either explicitly removed (RemTask() or DeleteTask()) or when it exits to Exec&#039;s default task removal code (RemTask()).&lt;br /&gt;
&lt;br /&gt;
Depending on the priority of a new task and the priorities of other tasks in the system, the newly added task may begin execution immediately.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Sharing Library Pointers&#039;&#039; Although in most cases it is possible for a parent task to pass a library base to a child task so the child can use that library, for some libraries, this is not possible. For this reason, the only library base sharable between tasks is Exec&#039;s library base.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Here is an example of simple task creation. In this example there is no coordination or communication between the main process and the simple task it has created. A more complex example might use named ports and messages to coordinate the activities and shutdown of two tasks. Because our task is very simple and never calls any system functions which could cause it to be signaled or awakened, we can safely remove the task at any time.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Keep This In Mind.&#039;&#039; Because the simple task&#039;s code is a function in our program, we must stop the subtask before exiting.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// simpletask.c - Uses the function CreateTaskTags() to create a simple subtask.&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/tasks.h&amp;gt;&lt;br /&gt;
#include &amp;lt;libraries/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define STACK_SIZE 16000&lt;br /&gt;
&lt;br /&gt;
uint32 sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
/* our function prototypes */&lt;br /&gt;
void simpletask(void);&lt;br /&gt;
void cleanexit(CONST_STRPTR, int32);&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
    struct Task *task = CreateTaskTags(&amp;quot;SimpleTask&amp;quot;, 0 , simpletask, STACK_SIZE,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if(task == NULL)  cleanexit(&amp;quot;Can&#039;t create task&amp;quot;, RETURN_FAIL);&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;This program initialized a variable to zero, then started a\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;separate task which is incrementing that variable right now,\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;while this program waits for you to press RETURN.\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Press RETURN now: &amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;FGetC( IDOS-&amp;gt;Input() );&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;The shared variable now equals %ld\n&amp;quot;, sharedvar);&lt;br /&gt;
&lt;br /&gt;
    /* We can simply remove the task we added because our simpletask does not make */&lt;br /&gt;
    /* any system calls which could cause it to be awakened or signalled later.    */&lt;br /&gt;
    IExec-&amp;gt;Forbid();&lt;br /&gt;
    IEXec-&amp;gt;DeleteTask(task);&lt;br /&gt;
    IExec-&amp;gt;Permit();&lt;br /&gt;
    cleanexit(&amp;quot;&amp;quot;, RETURN_OK);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void simpletask(void)&lt;br /&gt;
{&lt;br /&gt;
    while(sharedvar &amp;lt; 0x8000000) sharedvar++;&lt;br /&gt;
    /* Wait forever because main() is going to RemTask() us */&lt;br /&gt;
    IExec-&amp;gt;Wait(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void cleanexit(CONST_STRPTR s, int32 e)&lt;br /&gt;
{&lt;br /&gt;
    if(s != NULL) IDOS-&amp;gt;Printf(&amp;quot;%s\n&amp;quot;, s);&lt;br /&gt;
    exit(e);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Stack ===&lt;br /&gt;
&lt;br /&gt;
Every task requires a stack. All normal code execution occurs on this task stack. Special modes of execution (processor traps and system interrupts for example) execute on a single &#039;&#039;supervisor mode&#039;&#039; stack and do not directly affect task stacks.&lt;br /&gt;
&lt;br /&gt;
Task stacks are normally used to store local variables, subroutine return addresses, and saved register values. Additionally, when a task loses the processor, all of its current registers are preserved on this stack (with the exception of the stack pointer itself, which must be saved in the task structure).&lt;br /&gt;
&lt;br /&gt;
The amount of stack used by a task can vary widely. As a general rule of thumb, a minimum stack size of 16000 bytes should be used. If any GUI elements are involved, a minimum stack size of 80000 bytes is recommended.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Because stack-bounds checking is not provided as a service of Exec, it is important to provide enough space for your task stack. Stack overflows are always difficult to debug and may result not only in the erratic failure of your task but also in the mysterious malfunction of other Amiga subsystems.&#039;&#039;&#039; Some compilers may provide a stack-checking option.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;You Can&#039;t Always Check The Stack.&#039;&#039; Such stack-checking options generally cannot be used if part of your code will be running on the system stack (interrupts, exceptions, handlers, servers) or on a different task&#039;s stack (libraries, devices, created tasks).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When choosing your stack size, do not cut it too close. Remember that any recursive routines in your code may use varying amounts of stack, and that future versions of system routines may use additional stack variables. By dynamically allocating buffers and arrays, most application programs can be designed to function comfortably within the recommended minimum process stack size of 16000 bytes.&lt;br /&gt;
&lt;br /&gt;
=== Task Priority ===&lt;br /&gt;
&lt;br /&gt;
A task&#039;s priority indicates its importance relative to other tasks. Higher-priority tasks receive the processor before lower-priority tasks do. Task priority is stored as a signed number ranging from -128 to +127. Higher priorities are represented by more positive values; zero is considered the neutral priority. Normally, system tasks execute somewhere in the range of +20 to -20, and most application tasks execute at priority 0.&lt;br /&gt;
&lt;br /&gt;
It is not wise to needlessly raise a task&#039;s priority. Sometimes it may be necessary to carefully select a priority so that the task can properly interact with various system tasks. The SetTaskPri() Exec function is provided for this purpose.&lt;br /&gt;
&lt;br /&gt;
== Task Termination ==&lt;br /&gt;
&lt;br /&gt;
Task termination may occur as the result of a number of situations:&lt;br /&gt;
&lt;br /&gt;
* A program returning from its initialPC routine and dropping into its finalPC routine or the system default finalizer.&lt;br /&gt;
* A task trap that is too serious for a recovery action. This includes traps like processor bus error, odd address access errors, etc.&lt;br /&gt;
* A trap that is not handled by the task. For example, the task might be terminated if your code happened to encounter a processor TRAP instruction and you did not provide a trap handling routine.&lt;br /&gt;
* An explicit call to RemTask() or DeleteTask().&lt;br /&gt;
&lt;br /&gt;
Task termination involves the deallocation of system resources and the removal of the task structure from Exec. The most important part of task termination is the deallocation of system resources. A task must return all memory that it allocated for its private use, it must terminate any outstanding I/O commands, and it must close access to any system libraries or devices that it has opened.&lt;br /&gt;
&lt;br /&gt;
It is wise to adopt a strategy for task clean-up responsibility. You should decide whether resource allocation and deallocation is the duty of the creator task or the newly created task. Often it is easier and safer for the creator to handle the resource allocation and deallocation on behalf of its offspring. In such cases, before removing the child task, you must make sure it is in a safe state such as Wait(0L) and not still using a resources or waiting for an event or signal that might still occur.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitabl&amp;quot;&lt;br /&gt;
| &#039;&#039;NOTE:&#039;&#039; Certain resources, such as signals and created ports, must be allocated and deallocated by the same task that will wait on them. Also note that if your subtask code is part of your loaded program, you must not allow your program to exit before its subtasks have cleaned up their allocations, and have been either deleted or placed in a safe state such as Wait(0).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Exclusion ==&lt;br /&gt;
&lt;br /&gt;
From time to time the advanced system program may find it necessary to access global system data structures. Because these structures are shared by the system and by other tasks that execute asynchronously to your task, a task must prevent other tasks from using these structures while it is reading from or writing to them. Task exclusion methods described below prevent the operating system from switching tasks by &#039;&#039;forbidding&#039;&#039; or &#039;&#039;disabling&#039;&#039;. A section of code that requires the use of either of these mechanisms to lock out access by others is termed a &#039;&#039;critical section&#039;&#039;. &#039;&#039;&#039;Use of these methods is discouraged. For arbitrating access to data between your tasks, &#039;&#039;mutexes&#039;&#039; are a superior solution (See [[Exec_Mutexes|Exec Mutexes]]). The below solution is deprecated and will be removed from the AmigaOS soon.&lt;br /&gt;
&lt;br /&gt;
=== Forbidding Task Switching ===&lt;br /&gt;
&lt;br /&gt;
Forbidding is used when a task is accessing shared structures that might also be accessed at the same time from another task. It effectively eliminates the possibility of simultaneous access by imposing &#039;&#039;non-preemptive&#039;&#039; task scheduling. &#039;&#039;&#039;This has the net effect of disabling multitasking for as long as your task remains in its running state.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
While forbidden, your task will continue running until it performs a call to Wait() or exits from the forbidden state. Interrupts will occur normally, but no new tasks will be dispatched, &#039;&#039;regardless of their priorities&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a task running in the forbidden state calls the Wait() function, directly or indirectly, it implies a temporary exit from its forbidden state. Since almost all stdio, device I/O, and file I/O functions must Wait() for I/O completion, performing such calls will cause your task to Wait(), temporarily breaking the forbid. While the task is waiting, the system will perform normally. When the task receives one of the signals it is waiting for, it will again reenter the forbidden state.&lt;br /&gt;
&lt;br /&gt;
To become forbidden, a task calls the Forbid() function. To escape, the Permit() function is used. The use of these functions may be nested with the expected affects; you will not exit the forbidden mode until you call the outermost Permit().&lt;br /&gt;
&lt;br /&gt;
As an example, the Exec task list should only be accessed when in a Forbid() state. Accessing the list without forbidding could lead to incorrect results or it could crash the entire system. To access the task list also requires the program to disable interrupts which is discussed in the next section.&lt;br /&gt;
&lt;br /&gt;
=== Disabling Tasks ===&lt;br /&gt;
&lt;br /&gt;
Disabling is similar to forbidding, but it also prevents interrupts from occurring during a critical section. Disabling is required when a task accesses structures that are shared by interrupt code. It eliminates the possibility of an interrupt accessing shared structures by preventing interrupts from occurring. Use of disabling is strongly discouraged.&lt;br /&gt;
&lt;br /&gt;
To disable interrupts you can call the Disable() function. To enable interrupts again, use the Enable() function.&lt;br /&gt;
&lt;br /&gt;
Like forbidden sections, disabled sections can be nested. To restore normal interrupt processing, an Enable() call must be made for every Disable(). Also like forbidden sections, any direct or indirect call to the Wait() function will enable interrupts until the task regains the processor.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;WARNING: It is important to realize that there is a danger in using disabled sections&#039;&#039;. Because the software on the Amiga depends heavily on its interrupts occurring in nearly real time, you cannot disable for more than a very brief instant. Disabling interrupts for more than 250 microseconds can interfere with the normal operation of vital system functions, especially serial I/O.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It is never necessary to both Disable() and Forbid(). Because disabling prevents interrupts, it also prevents preemptive task scheduling. When disable is used within an interrupt, it will have the effect of locking out all higher level interrupts (lower level interrupts are automatically disabled by the CPU). Many Exec lists can only be accessed while disabled. Suppose you want to print the names of all system tasks. You would need to access both the TaskReady and TaskWait lists from within a single disabled section. In addition, you must avoid calling system functions that would break a disable by an indirect call to Wait() (Printf() for example). In this example, the names are gathered into a list while task switching is disabled. Then task switching is enabled and the names are printed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// tasklist.c - Snapshots and prints the ExecBase task list&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/lists.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/nodes.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/execbase.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/utility.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CONST_STRPTR VersTag = &amp;quot;$VER: tasklist 53.1 (21.6.2012)&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
extern struct ExecBase *SysBase;&lt;br /&gt;
&lt;br /&gt;
/* Use extended structure to hold task information */&lt;br /&gt;
struct TaskNode {&lt;br /&gt;
    struct Node tn_Node;&lt;br /&gt;
    uint32 tn_TaskAddress;&lt;br /&gt;
    uint32 tn_SigAlloc;&lt;br /&gt;
    uint32 tn_SigWait;&lt;br /&gt;
    TEXT tn_Name[32];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    struct TaskNode *tnode = NULL;&lt;br /&gt;
    struct TaskNode *rnode = NULL;&lt;br /&gt;
&lt;br /&gt;
    /* Allocate memory for our list */&lt;br /&gt;
    struct List *ourtasklist = IExec-&amp;gt;AllocSysObjectTags(ASOT_LIST, TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ourtasklist != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        /* Make sure tasks won&#039;t switch lists or go away */&lt;br /&gt;
        IExec-&amp;gt;Disable();&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task WAIT list */&lt;br /&gt;
        struct List *exectasklist = &amp;amp;(SysBase-&amp;gt;TaskWait);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
&lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
            }&lt;br /&gt;
            else break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task READY list */&lt;br /&gt;
        exectasklist = &amp;amp;(SysBase-&amp;gt;TaskReady);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
        &lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizoeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
                if(!rnode)  rnode = tnode;  /* first READY task */&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Re-enable interrupts and taskswitching */&lt;br /&gt;
        IExec-&amp;gt;Enable();&lt;br /&gt;
&lt;br /&gt;
        /* Print now (printing above would have defeated a Forbid or Disable) */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Pri Address     SigAlloc    SigWait    Taskname\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        struct TaskNode *node = (struct TaskNode *)IExec-&amp;gt;GetHead(ourtasklist);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nWAITING:\n&amp;quot;);&lt;br /&gt;
        while (tnode = (struct TaskNode *)IExec-&amp;gt;GetSucc((struct Node*)node))&lt;br /&gt;
        {&lt;br /&gt;
            if(tnode == rnode)&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;\nREADY:\n&amp;quot;);  /* we set rnode above */&lt;br /&gt;
                &lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                    node-&amp;gt;tn_Node.ln_Pri, node-&amp;gt;tn_TaskAddress, node-&amp;gt;tn_SigAlloc,&lt;br /&gt;
                    node-&amp;gt;tn_SigWait, node-&amp;gt;tn_Name);&lt;br /&gt;
&lt;br /&gt;
            /* Free the memory, no need to remove the node, referenced once only */&lt;br /&gt;
            IExec-&amp;gt;FreeVec(node);&lt;br /&gt;
            node = tnode;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_LIST, ourtasklist);&lt;br /&gt;
&lt;br /&gt;
        /* Say who we are */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nTHIS TASK:\n&amp;quot;);&lt;br /&gt;
        struct Task *task = FindTask(NULL);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                task-&amp;gt;tc_Node.ln_Pri, task, task-&amp;gt;tc_SigAlloc,&lt;br /&gt;
                task-&amp;gt;tc_SigWait, task-&amp;gt;tc_Node.ln_Name);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Mutexes and Semaphores ===&lt;br /&gt;
&lt;br /&gt;
Mutexes and semaphores can be used for the purposes of mutual exclusion. With this method of locking, all tasks agree on a locking convention before accessing shared data structures. Tasks that do not require access are not affected and will run normally, so this type of exclusion is considered preferable to forbidding and disabling. This form of exclusion is explained in more detail in [[Exec_Mutexes|Mutexes]] and [[Exec_Semaphores|Semaphores]].&lt;br /&gt;
&lt;br /&gt;
== Task Exceptions ==&lt;br /&gt;
&lt;br /&gt;
Exec can provide a task with its own task-local &amp;quot;interrupt&amp;quot; called an &#039;&#039;exception&#039;&#039;. When some exceptional event occurs, an Exec exception occurs which stops a particular task from executing its normal code and forces it to execute a special, task-specific exception handling routine.&lt;br /&gt;
&lt;br /&gt;
To set up an exception routine for a task requires setting values in the task&#039;s control structure (the Task structure). The tc_ExceptCode field should point to the task&#039;s exception handling routine. If this field is zero, Exec will ignore all exceptions. The tc_ExceptData field should point to any data the exception routine needs.&lt;br /&gt;
&lt;br /&gt;
Exec exceptions work using signals. When a specific signal or signals occur, Exec will stop a task and execute its exception routine. Use the Exec function SetExcept() to tell Exec which of the task&#039;s signals should trigger the exception.&lt;br /&gt;
&lt;br /&gt;
When an exception occurs, Exec stops executing the tasks normal code and jumps immediately into the exception routine, no matter what the task was doing. The exception routine operates in the same context the task&#039;s normal code; it operates in the CPU&#039;s user mode and uses the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
Before entering the exception routine, Exec pushes the normal task code&#039;s context onto the stack. Exec then puts certain parameters in the processor registers for the exception routine to use. D0 contains a signal mask indicating which signal bit or bits caused the exception. Exec disables these signals when the task enters its exception routine. If more than one signal bit is set (i.e. if two signals occurred simultaneously), it is up to the exception routine to decide in what order to process the two different signals. A1 points to the related exception data (from tc_ExceptData), and A6 contains the Exec library base. You can think of an exception as a subtask outside of your normal task. Because task exception code executes in &#039;&#039;user&#039;&#039; mode, however, the task stack must be large enough to supply the extra space consumed during an exception.&lt;br /&gt;
&lt;br /&gt;
While processing a given exception, Exec prevents that exception from occurring recursively. At exit from your exception-processing code, you should make sure D0 contains the signal mask the exception routine received in D0 because Exec looks here to see which signals it should reactivate. When the task executes the RTS instruction at the end of the exception routine, the system restores the previous contents of all of the task registers and resumes the task at the point where it was interrupted by the exception signal.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Exceptions Are Tricky&#039;&#039;. Exceptions are difficult to use safely. An exception can interrupt a task that is executing a critical section of code within a system function, or one that has locked a system resource such as the disk or blitter (note that even simple text output uses the blitter.) This possibility makes it dangerous to use most system functions within an exception unless you are sure that your interrupted task was performing only local, non-critical operations.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Traps ==&lt;br /&gt;
&lt;br /&gt;
Task &#039;&#039;traps&#039;&#039; are synchronous exceptions to the normal flow of program control. They are always generated as a direct result of an operation performed by your program&#039;s code. Whether they are accidental or purposely generated, they will result in your program being forced into a special condition in which it must immediately handle the trap. Address error, privilege violation, zero divide, and trap instructions all result in task traps. They may be generated directly by the &#039;&#039;68000&#039;&#039; processor (Motorola calls them &amp;quot;exceptions&amp;quot;) or simulated by software.&lt;br /&gt;
&lt;br /&gt;
A task that incurs a trap has no choice but to respond immediately. The task must have a module of code to handle the trap. Your task may be aborted if a trap occurs and no means of handling it has been provided. Default trap handling code (tc_TrapCode) is provided by the OS. You may instead choose to do your own processing of traps. The tc_TrapCode field is the address of the handler that you have designed to process the trap. The tc_TrapData field is the address of the data area for use by the trap handler.&lt;br /&gt;
&lt;br /&gt;
The system&#039;s default trap handling code generally displays a Software Error Requester or Alert containing an exception number and the program counter or task address. Processor exceptions generally have numbers in the range hex 00 to 2F. The &#039;&#039;68000&#039;&#039; processor exceptions of particular interest are as follows.&lt;br /&gt;
&lt;br /&gt;
Traps (68000 Exception Vector Numbers)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 2 || Bus error || access of nonexistent memory&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Address error || long/word access of odd address (&#039;&#039;68000&#039;&#039;)&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Illegal instruction || illegal opcode (other than Axxx or Fxxx)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Zero divide || processor division by zero&lt;br /&gt;
|-&lt;br /&gt;
| 6 || CHK instruction || register bounds error trap by CHK&lt;br /&gt;
|-&lt;br /&gt;
| 7 || TRAPV instruction || overflow error trap by TRAPV&lt;br /&gt;
|-&lt;br /&gt;
| 8 || Privilege violation || user execution of supervisor opcode&lt;br /&gt;
|-&lt;br /&gt;
| 9 || Trace || status register TRACE bit trap&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Line 1010 emulator || execution of opcode beginning with $A&lt;br /&gt;
|-&lt;br /&gt;
| 11 || Line 1111 emulator || execution of opcode beginning with $F&lt;br /&gt;
|-&lt;br /&gt;
| 32-47 || Trap instructions || TRAP &#039;&#039;N&#039;&#039; instruction where &#039;&#039;N&#039;&#039; = 0 to 15&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A system alert for a processor exception may set the high bit of the longword exception number to indicate an unrecoverable error (for example $8000¬†0005 for an unrecoverable processor exception #5). System alerts with more complex numbers are generally Amiga-specific software failures. These are built from the definitions in the &amp;amp;lt;exec/alerts.h&amp;amp;gt; include file.&lt;br /&gt;
&lt;br /&gt;
The actual stack frames generated for these traps are processor-dependent. The &#039;&#039;68010&#039;&#039;, &#039;&#039;68020&#039;&#039;, and &#039;&#039;68030&#039;&#039; processors will generate a different type of stack frame than the &#039;&#039;68000&#039;&#039;. If you plan on having your program handle its own traps, you should not make assumptions about the format of the supervisor stack frame. Check the flags in the AttnFlags field of the ExecBase structure for the type of processor in use and process the stack frame accordingly.&lt;br /&gt;
&lt;br /&gt;
=== Trap Handlers ===&lt;br /&gt;
&lt;br /&gt;
For compatibility with the &#039;&#039;68000&#039;&#039;, Exec performs trap handling in supervisor mode. This means that all task switching is disabled during trap handling.&lt;br /&gt;
&lt;br /&gt;
At entry to the task&#039;s trap handler, the system stack contains a processor-dependent trap frame as defined in the &#039;&#039;68000&#039;&#039;/&#039;&#039;10&#039;&#039;/&#039;&#039;20&#039;&#039;/&#039;&#039;30&#039;&#039; manuals. A longword exception number is added to this frame. That is, when a handler gains control, the top of stack contains the exception number and the trap frame immediately follows.&lt;br /&gt;
&lt;br /&gt;
To return from trap processing, remove the exception number from the stack (note that this is the supervisor stack, not the user stack) and then perform a return from exception (RTE).&lt;br /&gt;
&lt;br /&gt;
Because trap processing takes place in supervisor mode, with task dispatching disabled, it is strongly urged that you keep trap processing as short as possible or switch back to user mode from within your trap handler. If a trap handler already exists when you add your own trap handler, it is smart to propagate any traps that you do not handle down to the previous handler. This can be done by saving the previous address from tc_TrapCode and having your handler pass control to that address if the trap which occurred is not one you wish to handle.&lt;br /&gt;
&lt;br /&gt;
The following example installs a simple trap handler which intercepts processor divide-by-zero traps, and passes on all other traps to the previous default trap code. The example has two code modules which are linked together. The trap handler code is in assembler. The C module installs the handler, demonstrates its effectiveness, then restores the previous tc_TrapCode.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* trap_c.c - Execute me to compile me with SAS C 5.10&lt;br /&gt;
LC -b0 -cfistq -v -y -j73 trap_c.c&lt;br /&gt;
Blink FROM LIB:c.o,trap_c.o,trap_a.o TO trap LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit&lt;br /&gt;
&lt;br /&gt;
trap_c.c - C module of sample integer divide-by-zero trap&lt;br /&gt;
*/&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;exec/tasks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdlib.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdio.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE&lt;br /&gt;
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */&lt;br /&gt;
int chkabort(void) {return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
extern ULONG trapa();           /* assembler trap code in trap_a.asm */&lt;br /&gt;
&lt;br /&gt;
APTR oldTrapCode;&lt;br /&gt;
ULONG countdiv0;&lt;br /&gt;
&lt;br /&gt;
void main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
    struct Task *thistask;&lt;br /&gt;
    ULONG k,j;&lt;br /&gt;
&lt;br /&gt;
    thistask = FindTask(NULL);&lt;br /&gt;
&lt;br /&gt;
    /* Save our task&#039;s current trap code pointer */&lt;br /&gt;
    oldTrapCode = thistask-&amp;amp;gt;tc_TrapCode;&lt;br /&gt;
&lt;br /&gt;
    /* Point task to our assembler trap handler code.  Ours will just count */&lt;br /&gt;
    /* divide-by-zero traps, and pass other traps on to the normal TrapCode */&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = (APTR)trapa;&lt;br /&gt;
&lt;br /&gt;
    countdiv0 = 0L;&lt;br /&gt;
&lt;br /&gt;
    for(k=0; k&amp;amp;lt;4; k++)            /* Let&#039;s divide by zero a few times */&lt;br /&gt;
       {&lt;br /&gt;
       printf(&amp;amp;quot;dividing %ld by zero... &amp;amp;quot;,k);&lt;br /&gt;
       j = k/0L;&lt;br /&gt;
       printf(&amp;amp;quot;did it\n&amp;amp;quot;);&lt;br /&gt;
       }&lt;br /&gt;
    printf(&amp;amp;quot;\nDivide by zero happened %ld times\n&amp;amp;quot;,countdiv0);&lt;br /&gt;
&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = oldTrapCode;     /* Restore old trap code */&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;* trap_a.asm - Example trap handling code (leaves D0 intact).  Entered&lt;br /&gt;
* in supervisor mode with the following on the supervisor stack:&lt;br /&gt;
*    0(sp).l = trap#&lt;br /&gt;
*    4(sp) Processor dependent exception frame&lt;br /&gt;
&lt;br /&gt;
    INCLUDE &amp;amp;quot;exec/types.i&amp;amp;quot;&lt;br /&gt;
    INCLUDE &amp;amp;quot;libraries/dos.i&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
        XDEF _trapa&lt;br /&gt;
        XREF _countdiv0&lt;br /&gt;
        XREF _oldTrapCode&lt;br /&gt;
&lt;br /&gt;
        CODE&lt;br /&gt;
_trapa:                                 ; our trap handler entry&lt;br /&gt;
        CMPI.L  #5,(SP)                 ; is this a divide by zero ?&lt;br /&gt;
        BNE.S   notdiv0                 ; no&lt;br /&gt;
        ADD.L   #1,_countdiv0           ; yes, increment our div0 count&lt;br /&gt;
endtrap:&lt;br /&gt;
        ADDQ    #4,SP                   ; remove exception number from SSP&lt;br /&gt;
        RTE                             ; return from exception&lt;br /&gt;
notdiv0:&lt;br /&gt;
        TST.L   _oldTrapCode            ; is there another trap handler ?&lt;br /&gt;
        BEQ.S   endtrap                 ; no, so we&#039;ll exit&lt;br /&gt;
        MOVE.L  _oldTrapCode,-(SP)      ; yes, go on to old TrapCode&lt;br /&gt;
        RTS                             ; jumps to old TrapCode&lt;br /&gt;
&lt;br /&gt;
        END&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Trap Instructions ===&lt;br /&gt;
&lt;br /&gt;
The TRAP instructions in the &#039;&#039;68000&#039;&#039; generate traps 32-47. Because many independent pieces of system code may desire to use these traps, the AllocTrap() and FreeTrap() functions are provided. These work in a fashion similar to that used by AllocSignal() and FreeSignal(), mentioned above.&lt;br /&gt;
&lt;br /&gt;
Allocating a trap is simply a bookkeeping job within a task. It does not affect how the system calls the trap handler; it helps coordinate who owns what traps. Exec does nothing to determine whether or not a task is prepared to handle a particular trap. It simply calls your code. It is up to your program to handle the trap.&lt;br /&gt;
&lt;br /&gt;
To allocate any trap, you can use the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(-1)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;all trap instructions are in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can select a specific trap using this code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(3)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;trap #3 is in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To free a trap, you use the FreeTrap() function passing it the trap number to be freed.&lt;br /&gt;
&lt;br /&gt;
== Processor and Cache Control ==&lt;br /&gt;
&lt;br /&gt;
Exec provides a number of to control the processor mode and, if available, the caches. All these functions work independently of the specific &#039;&#039;M68000&#039;&#039; family processor type. This enables you to write code which correctly controls the state of both the &#039;&#039;MC68000&#039;&#039; and the &#039;&#039;MC68040&#039;&#039;. Along with processor mode and cache control, functions are provided to obtain information about the condition code register (CCR) and status register (SR). No functions are provided to control a paged memory management unit (PMMU) or floating point unit (FPU).&lt;br /&gt;
&lt;br /&gt;
Processor and Cache Control Functions&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| GetCC() || Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR() || Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState() || Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor() || Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState() || Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE() || Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU() || Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl() || Global cache control.&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA() || Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA() || Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Supervisor Mode ===&lt;br /&gt;
&lt;br /&gt;
While in supervisor mode, you have complete access to all data and registers, including those used for task scheduling and exceptions, and can execute privileged instructions. In application programs, normally only task trap code is directly executed in supervisor mode, to be compatible with the &#039;&#039;MC68000&#039;&#039;. For normal applications, it should never be necessary to switch to supervisor mode itself, only indirectly through Exec function calls. Remember that task switching is disabled while in supervisor mode. If it is absolutely needed to execute code in supervisor mode, keep it as brief as possible.&lt;br /&gt;
&lt;br /&gt;
Supervisor mode can only be entered when a &#039;&#039;680x0&#039;&#039; exception occurs (an interrupt or trap). The Supervisor() function allows you to trap an exception to a specified assembly function. In this function your have full access to all registers. No registers are saved when your function is invoked. You are responsible for restoring the system to a sane state when you are done. You must return to user mode with an RTE instruction. You must not return to user mode by executing a privileged instruction which clears the supervisor bit in the status register. Refer to a manual on the &#039;&#039;M68000&#039;&#039; family of CPUs for information about supervisor mode and available privileged instructions per processor type.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;MC68000&#039;&#039; has two stacks, the user stack (USP) and supervisor stack (SSP). As of the &#039;&#039;MC68020&#039;&#039; there are two supervisor stacks, the interrupt stack pointer (ISP) and the master stack pointer (MSP). The SuperState() function allows you to enter supervisor mode with the USP used as SSP. The function returns the SSP, which will be the MSP, if an &#039;&#039;MC68020&#039;&#039; or greater is used. Returning to user mode is done with the UserState() function. This function takes the SSP as argument, which must be saved when SuperState() is called. Because of possible problems with stack size, Supervisor() is to be preferred over SuperState().&lt;br /&gt;
&lt;br /&gt;
=== Status Register ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The processor status register bits can be set or read with the SetSR() function. This function operates in supervisor mode, thus both the upper and lower byte of the SR can be read or set. Be very sure you know what you are doing when you use this function to set bits in the SR and above all never try to use this function to enter supervisor mode. Refer to the &#039;&#039;M68000 Programmers Reference Manual&#039;&#039; by Motorola Inc. for information about the definition of individual SR bits per processor type.&lt;br /&gt;
&lt;br /&gt;
=== Condition Code Register ===&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;MC68000&#039;&#039; a copy of the processor condition codes can be obtained with the MOVE SR,&amp;amp;lt;ea&amp;amp;gt; instruction. On &#039;&#039;MC68010&#039;&#039; processors and up however, the instruction MOVE CCR,&amp;amp;lt;ea&amp;amp;gt; must be used. Using the specific &#039;&#039;MC68000&#039;&#039; instruction on later processors will cause a &#039;&#039;680x0&#039;&#039; exception since it is a privileged instruction on those processors. The GetCC() function provides a processor independent way of obtaining a copy of the condition codes. For all processors there are 5 bits which can indicate the result of an integer or a system control instruction:&lt;br /&gt;
&lt;br /&gt;
* X - extend&lt;br /&gt;
* N - negative&lt;br /&gt;
* Z - zero&lt;br /&gt;
* V - overflow&lt;br /&gt;
* C - carry&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;X&#039;&#039;&#039; bit is used for multiprecision calculations. If used, it is copy of the carry bit. The other bits state the result of a processor operation.&lt;br /&gt;
&lt;br /&gt;
=== Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
As of the &#039;&#039;MC68020&#039;&#039; all processors have an instruction cache, 256 bytes on the &#039;&#039;MC68020&#039;&#039; and &#039;&#039;MC68030&#039;&#039; and 4 KBytes on a &#039;&#039;MC68040&#039;&#039;. The &#039;&#039;MC68030&#039;&#039; and &#039;&#039;MC68040&#039;&#039; have data caches as well, 256 bytes and 4 KBytes respectively. All the processors load instructions ahead of the program counter (PC), albeit it that the &#039;&#039;MC68000&#039;&#039; and &#039;&#039;MC68010&#039;&#039; only prefetch one and two words respectively. This means the CPU loads instructions ahead of the current program counter. For this reason self-modifying code is strongly discouraged. If your code modifies or decrypts itself just ahead of the program counter, the pre-fetched instructions may not match the modified instructions. If self-modifying code must be used, flushing the cache is the safest way to prevent this.&lt;br /&gt;
&lt;br /&gt;
=== DMA Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
The CachePreDMA() and CachePostDMA() functions allow you to flush the data cache before and after Direct Memory Access. Typically only DMA device drivers benefit from this. These functions take the processor type, possible MMU and cache mode into account. When no cache is available they end up doing nothing. These functions can be replaced with ones suitable for different cache hardware. Refer to the SDK for implementation specifics.&lt;br /&gt;
&lt;br /&gt;
Since DMA device drivers read and write directly to memory, they are effected by the CopyBack feature of the &#039;&#039;MC68040&#039;&#039; (explained below). Using DMA with CopyBack mode requires a cache flush. If a DMA device needs to read RAM via DMA, it must make sure that the data in the caches has been written to memory first, by calling CachePreDMA(). In case of a write to memory, the DMA device should first clear the caches with CachePreDMA(), write the data and flush the caches again with CachePostDMA().&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following chart gives a brief description of the Exec functions that control tasks. See the SDK for details about each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Exec Task Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AddTask()&lt;br /&gt;
| Add a task to the system.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTrap()&lt;br /&gt;
| Allocate a processor trap vector.&lt;br /&gt;
|-&lt;br /&gt;
| Disable()&lt;br /&gt;
| Disable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| Enable()&lt;br /&gt;
| Enable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| FindTask()&lt;br /&gt;
| Find a specific task.&lt;br /&gt;
|-&lt;br /&gt;
| Forbid()&lt;br /&gt;
| Forbid task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| FreeTrap()&lt;br /&gt;
| Release a process trap.&lt;br /&gt;
|-&lt;br /&gt;
| Permit()&lt;br /&gt;
| Permit task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| SetTaskPri()&lt;br /&gt;
| Set the priority of a task.&lt;br /&gt;
|-&lt;br /&gt;
| RemTask()&lt;br /&gt;
| Remove a task from the system.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE()&lt;br /&gt;
| Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU()&lt;br /&gt;
| Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl()&lt;br /&gt;
| Global cache control (V37).&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA()&lt;br /&gt;
| Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA()&lt;br /&gt;
| Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| GetCC()&lt;br /&gt;
| Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR()&lt;br /&gt;
| Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState()&lt;br /&gt;
| Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor()&lt;br /&gt;
| Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState()&lt;br /&gt;
| Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CreateTask()&lt;br /&gt;
| Setup and add a new task.&lt;br /&gt;
|-&lt;br /&gt;
| DeleteTask()&lt;br /&gt;
| Delete a task created with CreateTask().&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3419</id>
		<title>Exec Tasks</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3419"/>
		<updated>2012-07-10T06:15:01Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Task Exclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{NeedUpdate}}&lt;br /&gt;
== Exec Tasks ==&lt;br /&gt;
&lt;br /&gt;
One of the most powerful features of the Amiga operating system is its ability to run and manage multiple independent program tasks, providing each task with processor time based on their priority and activity. These tasks include system device drivers, background utilities, and user interface environments, as well as normal application programs. This multitasking capability is provided by the Exec library&#039;s management of task creation, termination, scheduling, event signals, traps, exceptions, and mutual exclusion.&lt;br /&gt;
&lt;br /&gt;
This section deals with Exec on a lower level than most applications programmers need and assumes you are already familiar with the Exec basics discussed in the [[Introduction_to_Exec|Introduction to Exec]].&lt;br /&gt;
&lt;br /&gt;
== Task Structure ==&lt;br /&gt;
&lt;br /&gt;
Exec maintains task context and state information in a task-control data structure. Like most Exec structures, Task structures are dynamically linked onto various task queues through the use of an embedded Exec list Node structure (see the Lists chapter). Any task can find its own task structure by calling FindTask(NULL). The C-language form of this structure is defined in the &amp;amp;lt;exec/tasks.h&amp;amp;gt; include file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Task  {&lt;br /&gt;
    struct Node tc_Node;&lt;br /&gt;
    UBYTE       tc_Flags;&lt;br /&gt;
    UBYTE       tc_State;&lt;br /&gt;
    BYTE        tc_IDNestCnt;   /* intr disabled nesting */&lt;br /&gt;
    BYTE        tc_TDNestCnt;   /* task disabled nesting */&lt;br /&gt;
    ULONG       tc_SigAlloc;    /* sigs allocated */&lt;br /&gt;
    ULONG       tc_SigWait;     /* sigs we are waiting for */&lt;br /&gt;
    ULONG       tc_SigRecvd;    /* sigs we have received */&lt;br /&gt;
    ULONG       tc_SigExcept;   /* sigs we will take excepts for */&lt;br /&gt;
    UWORD       tc_TrapAlloc;   /* traps allocated */&lt;br /&gt;
    UWORD       tc_TrapAble;    /* traps enabled */&lt;br /&gt;
    APTR        tc_ExceptData;  /* points to except data */&lt;br /&gt;
    APTR        tc_ExceptCode;  /* points to except code */&lt;br /&gt;
    APTR        tc_TrapData;    /* points to trap code */&lt;br /&gt;
    APTR        tc_TrapCode;    /* points to trap data */&lt;br /&gt;
    APTR        tc_SPReg;       /* stack pointer */&lt;br /&gt;
    APTR        tc_SPLower;     /* stack lower bound */&lt;br /&gt;
    APTR        tc_SPUpper;     /* stack upper bound + 2*/&lt;br /&gt;
    VOID      (*tc_Switch)();   /* task losing CPU */&lt;br /&gt;
    VOID      (*tc_Launch)();   /* task getting CPU */&lt;br /&gt;
    struct List tc_MemEntry;    /* allocated memory */&lt;br /&gt;
    APTR        tc_UserData;    /* per task data */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Most of these fields are not relevant for simple tasks; they are used by Exec for state and administrative purposes. A few fields, however, are provided for the advanced programs that support higher level environments (as in the case of &#039;&#039;processes&#039;&#039;) or require precise control (as in &#039;&#039;devices&#039;&#039;). The following sections explain these fields in more detail.&lt;br /&gt;
&lt;br /&gt;
== Task Creation ==&lt;br /&gt;
&lt;br /&gt;
To create a new task manually you must allocate a task structure, initialize its various fields, and then link it into Exec with a call to AddTask(). This is not the recommended way to create a task. This detailed information is only being provided for reference.&lt;br /&gt;
&lt;br /&gt;
The task structure may be allocated by calling the AllocVecTags() function with the MEMF_SHARED attribute and AVT_ClearWithValue tag.&lt;br /&gt;
&lt;br /&gt;
The Task fields that require initialization depend on how you intend to use the task. For the simplest of tasks, only a few fields must be initialized:&lt;br /&gt;
&lt;br /&gt;
; tc_Node&lt;br /&gt;
: The task list node structure. This includes the task&#039;s priority, its type, and its name (refer to [[Exec_Lists_and_Queues|Exec Lists and Queues]] chapter).&lt;br /&gt;
&lt;br /&gt;
; tc_SPLower&lt;br /&gt;
: The lower memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPUpper&lt;br /&gt;
: The upper memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPReg&lt;br /&gt;
: The initial stack pointer. Because task stacks grow &#039;&#039;downward&#039;&#039; in memory, this field is usually set to the same value as tc_SPUpper.&lt;br /&gt;
&lt;br /&gt;
Zeroing all other unused fields will cause Exec to supply the appropriate system default values. Allocating the structure with the AVT_ClearWithValue tag is an easy way to be sure that this happens.&lt;br /&gt;
&lt;br /&gt;
Once the structure has been initialized, it must be linked to Exec. This is done with a call to AddTask() in which the following parameters are specified:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
AddTask(struct Task *task, APTR initialPC, APTR finalPC )&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The task argument is a pointer to your initialized Task structure. Set initialPC to the entry point of your task code. This is the address of the first instruction the new task will execute.&lt;br /&gt;
&lt;br /&gt;
Set finalPC to the address of the finalization code for your task. This is a code section that will receive control if the initialPC routine ever performs a return. This exists to prevent your task from being launched into random memory upon an accidental return. The finalPC routine should usually perform various program-related clean-up duties and should then remove the task. If a zero is supplied for this parameter, Exec will use its default finalization code (which simply calls the RemTask() function).&lt;br /&gt;
&lt;br /&gt;
=== Task Creation With CreateTask() ===&lt;br /&gt;
&lt;br /&gt;
The recommended of creating a task is with CreateTask() or CreateTaskTags() functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
CreateTaskTags(CONST_STRPTR name, int32 priority, APTR initialPC, uint32 stacksize, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A task created with CreateTaskTags() may be removed with the DeleteTask() function, or it may simply return when it is finished. CreateTaskTags() adds a MemList to the tc_MemEntry of the task it creates, describing all memory it has allocated for the task, including the task stack and the Task structure itself. This memory will be deallocated by Exec when the task is either explicitly removed (RemTask() or DeleteTask()) or when it exits to Exec&#039;s default task removal code (RemTask()).&lt;br /&gt;
&lt;br /&gt;
Depending on the priority of a new task and the priorities of other tasks in the system, the newly added task may begin execution immediately.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Sharing Library Pointers&#039;&#039; Although in most cases it is possible for a parent task to pass a library base to a child task so the child can use that library, for some libraries, this is not possible. For this reason, the only library base sharable between tasks is Exec&#039;s library base.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Here is an example of simple task creation. In this example there is no coordination or communication between the main process and the simple task it has created. A more complex example might use named ports and messages to coordinate the activities and shutdown of two tasks. Because our task is very simple and never calls any system functions which could cause it to be signaled or awakened, we can safely remove the task at any time.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Keep This In Mind.&#039;&#039; Because the simple task&#039;s code is a function in our program, we must stop the subtask before exiting.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// simpletask.c - Uses the function CreateTaskTags() to create a simple subtask.&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/tasks.h&amp;gt;&lt;br /&gt;
#include &amp;lt;libraries/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define STACK_SIZE 16000&lt;br /&gt;
&lt;br /&gt;
uint32 sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
/* our function prototypes */&lt;br /&gt;
void simpletask(void);&lt;br /&gt;
void cleanexit(CONST_STRPTR, int32);&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
    struct Task *task = CreateTaskTags(&amp;quot;SimpleTask&amp;quot;, 0 , simpletask, STACK_SIZE,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if(task == NULL)  cleanexit(&amp;quot;Can&#039;t create task&amp;quot;, RETURN_FAIL);&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;This program initialized a variable to zero, then started a\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;separate task which is incrementing that variable right now,\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;while this program waits for you to press RETURN.\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Press RETURN now: &amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;FGetC( IDOS-&amp;gt;Input() );&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;The shared variable now equals %ld\n&amp;quot;, sharedvar);&lt;br /&gt;
&lt;br /&gt;
    /* We can simply remove the task we added because our simpletask does not make */&lt;br /&gt;
    /* any system calls which could cause it to be awakened or signalled later.    */&lt;br /&gt;
    IExec-&amp;gt;Forbid();&lt;br /&gt;
    IEXec-&amp;gt;DeleteTask(task);&lt;br /&gt;
    IExec-&amp;gt;Permit();&lt;br /&gt;
    cleanexit(&amp;quot;&amp;quot;, RETURN_OK);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void simpletask(void)&lt;br /&gt;
{&lt;br /&gt;
    while(sharedvar &amp;lt; 0x8000000) sharedvar++;&lt;br /&gt;
    /* Wait forever because main() is going to RemTask() us */&lt;br /&gt;
    IExec-&amp;gt;Wait(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void cleanexit(CONST_STRPTR s, int32 e)&lt;br /&gt;
{&lt;br /&gt;
    if(s != NULL) IDOS-&amp;gt;Printf(&amp;quot;%s\n&amp;quot;, s);&lt;br /&gt;
    exit(e);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Stack ===&lt;br /&gt;
&lt;br /&gt;
Every task requires a stack. All normal code execution occurs on this task stack. Special modes of execution (processor traps and system interrupts for example) execute on a single &#039;&#039;supervisor mode&#039;&#039; stack and do not directly affect task stacks.&lt;br /&gt;
&lt;br /&gt;
Task stacks are normally used to store local variables, subroutine return addresses, and saved register values. Additionally, when a task loses the processor, all of its current registers are preserved on this stack (with the exception of the stack pointer itself, which must be saved in the task structure).&lt;br /&gt;
&lt;br /&gt;
The amount of stack used by a task can vary widely. As a general rule of thumb, a minimum stack size of 16000 bytes should be used. If any GUI elements are involved, a minimum stack size of 80000 bytes is recommended.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Because stack-bounds checking is not provided as a service of Exec, it is important to provide enough space for your task stack. Stack overflows are always difficult to debug and may result not only in the erratic failure of your task but also in the mysterious malfunction of other Amiga subsystems.&#039;&#039;&#039; Some compilers may provide a stack-checking option.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;You Can&#039;t Always Check The Stack.&#039;&#039; Such stack-checking options generally cannot be used if part of your code will be running on the system stack (interrupts, exceptions, handlers, servers) or on a different task&#039;s stack (libraries, devices, created tasks).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When choosing your stack size, do not cut it too close. Remember that any recursive routines in your code may use varying amounts of stack, and that future versions of system routines may use additional stack variables. By dynamically allocating buffers and arrays, most application programs can be designed to function comfortably within the recommended minimum process stack size of 16000 bytes.&lt;br /&gt;
&lt;br /&gt;
=== Task Priority ===&lt;br /&gt;
&lt;br /&gt;
A task&#039;s priority indicates its importance relative to other tasks. Higher-priority tasks receive the processor before lower-priority tasks do. Task priority is stored as a signed number ranging from -128 to +127. Higher priorities are represented by more positive values; zero is considered the neutral priority. Normally, system tasks execute somewhere in the range of +20 to -20, and most application tasks execute at priority 0.&lt;br /&gt;
&lt;br /&gt;
It is not wise to needlessly raise a task&#039;s priority. Sometimes it may be necessary to carefully select a priority so that the task can properly interact with various system tasks. The SetTaskPri() Exec function is provided for this purpose.&lt;br /&gt;
&lt;br /&gt;
== Task Termination ==&lt;br /&gt;
&lt;br /&gt;
Task termination may occur as the result of a number of situations:&lt;br /&gt;
&lt;br /&gt;
* A program returning from its initialPC routine and dropping into its finalPC routine or the system default finalizer.&lt;br /&gt;
* A task trap that is too serious for a recovery action. This includes traps like processor bus error, odd address access errors, etc.&lt;br /&gt;
* A trap that is not handled by the task. For example, the task might be terminated if your code happened to encounter a processor TRAP instruction and you did not provide a trap handling routine.&lt;br /&gt;
* An explicit call to RemTask() or DeleteTask().&lt;br /&gt;
&lt;br /&gt;
Task termination involves the deallocation of system resources and the removal of the task structure from Exec. The most important part of task termination is the deallocation of system resources. A task must return all memory that it allocated for its private use, it must terminate any outstanding I/O commands, and it must close access to any system libraries or devices that it has opened.&lt;br /&gt;
&lt;br /&gt;
It is wise to adopt a strategy for task clean-up responsibility. You should decide whether resource allocation and deallocation is the duty of the creator task or the newly created task. Often it is easier and safer for the creator to handle the resource allocation and deallocation on behalf of its offspring. In such cases, before removing the child task, you must make sure it is in a safe state such as Wait(0L) and not still using a resources or waiting for an event or signal that might still occur.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitabl&amp;quot;&lt;br /&gt;
| &#039;&#039;NOTE:&#039;&#039; Certain resources, such as signals and created ports, must be allocated and deallocated by the same task that will wait on them. Also note that if your subtask code is part of your loaded program, you must not allow your program to exit before its subtasks have cleaned up their allocations, and have been either deleted or placed in a safe state such as Wait(0).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Exclusion ==&lt;br /&gt;
&lt;br /&gt;
From time to time the advanced system program may find it necessary to access global system data structures. Because these structures are shared by the system and by other tasks that execute asynchronously to your task, a task must prevent other tasks from using these structures while it is reading from or writing to them. Task exclusion method described below prevents the operating system from switching tasks by &#039;&#039;forbidding&#039;&#039; or &#039;&#039;disabling&#039;&#039;. A section of code that requires the use of either of these mechanisms to lock out access by others is termed a &#039;&#039;critical section&#039;&#039;. &#039;&#039;&#039;Use of these methods is discouraged. For arbitrating access to data between your tasks, &#039;&#039;mutexes&#039;&#039; are a superior solution (See [[Exec_Mutexes|Exec Mutexes]]). The below solution is deprecated and will be removed from the AmigaOS soon.&lt;br /&gt;
&lt;br /&gt;
=== Forbidding Task Switching ===&lt;br /&gt;
&lt;br /&gt;
Forbidding is used when a task is accessing shared structures that might also be accessed at the same time from another task. It effectively eliminates the possibility of simultaneous access by imposing &#039;&#039;non-preemptive&#039;&#039; task scheduling. &#039;&#039;&#039;This has the net effect of disabling multitasking for as long as your task remains in its running state.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
While forbidden, your task will continue running until it performs a call to Wait() or exits from the forbidden state. Interrupts will occur normally, but no new tasks will be dispatched, &#039;&#039;regardless of their priorities&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a task running in the forbidden state calls the Wait() function, directly or indirectly, it implies a temporary exit from its forbidden state. Since almost all stdio, device I/O, and file I/O functions must Wait() for I/O completion, performing such calls will cause your task to Wait(), temporarily breaking the forbid. While the task is waiting, the system will perform normally. When the task receives one of the signals it is waiting for, it will again reenter the forbidden state.&lt;br /&gt;
&lt;br /&gt;
To become forbidden, a task calls the Forbid() function. To escape, the Permit() function is used. The use of these functions may be nested with the expected affects; you will not exit the forbidden mode until you call the outermost Permit().&lt;br /&gt;
&lt;br /&gt;
As an example, the Exec task list should only be accessed when in a Forbid() state. Accessing the list without forbidding could lead to incorrect results or it could crash the entire system. To access the task list also requires the program to disable interrupts which is discussed in the next section.&lt;br /&gt;
&lt;br /&gt;
=== Disabling Tasks ===&lt;br /&gt;
&lt;br /&gt;
Disabling is similar to forbidding, but it also prevents interrupts from occurring during a critical section. Disabling is required when a task accesses structures that are shared by interrupt code. It eliminates the possibility of an interrupt accessing shared structures by preventing interrupts from occurring. Use of disabling is strongly discouraged.&lt;br /&gt;
&lt;br /&gt;
To disable interrupts you can call the Disable() function. To enable interrupts again, use the Enable() function.&lt;br /&gt;
&lt;br /&gt;
Like forbidden sections, disabled sections can be nested. To restore normal interrupt processing, an Enable() call must be made for every Disable(). Also like forbidden sections, any direct or indirect call to the Wait() function will enable interrupts until the task regains the processor.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;WARNING: It is important to realize that there is a danger in using disabled sections&#039;&#039;. Because the software on the Amiga depends heavily on its interrupts occurring in nearly real time, you cannot disable for more than a very brief instant. Disabling interrupts for more than 250 microseconds can interfere with the normal operation of vital system functions, especially serial I/O.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It is never necessary to both Disable() and Forbid(). Because disabling prevents interrupts, it also prevents preemptive task scheduling. When disable is used within an interrupt, it will have the effect of locking out all higher level interrupts (lower level interrupts are automatically disabled by the CPU). Many Exec lists can only be accessed while disabled. Suppose you want to print the names of all system tasks. You would need to access both the TaskReady and TaskWait lists from within a single disabled section. In addition, you must avoid calling system functions that would break a disable by an indirect call to Wait() (Printf() for example). In this example, the names are gathered into a list while task switching is disabled. Then task switching is enabled and the names are printed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// tasklist.c - Snapshots and prints the ExecBase task list&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/lists.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/nodes.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/execbase.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/utility.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CONST_STRPTR VersTag = &amp;quot;$VER: tasklist 53.1 (21.6.2012)&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
extern struct ExecBase *SysBase;&lt;br /&gt;
&lt;br /&gt;
/* Use extended structure to hold task information */&lt;br /&gt;
struct TaskNode {&lt;br /&gt;
    struct Node tn_Node;&lt;br /&gt;
    uint32 tn_TaskAddress;&lt;br /&gt;
    uint32 tn_SigAlloc;&lt;br /&gt;
    uint32 tn_SigWait;&lt;br /&gt;
    TEXT tn_Name[32];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    struct TaskNode *tnode = NULL;&lt;br /&gt;
    struct TaskNode *rnode = NULL;&lt;br /&gt;
&lt;br /&gt;
    /* Allocate memory for our list */&lt;br /&gt;
    struct List *ourtasklist = IExec-&amp;gt;AllocSysObjectTags(ASOT_LIST, TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ourtasklist != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        /* Make sure tasks won&#039;t switch lists or go away */&lt;br /&gt;
        IExec-&amp;gt;Disable();&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task WAIT list */&lt;br /&gt;
        struct List *exectasklist = &amp;amp;(SysBase-&amp;gt;TaskWait);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
&lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
            }&lt;br /&gt;
            else break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task READY list */&lt;br /&gt;
        exectasklist = &amp;amp;(SysBase-&amp;gt;TaskReady);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
        &lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizoeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
                if(!rnode)  rnode = tnode;  /* first READY task */&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Re-enable interrupts and taskswitching */&lt;br /&gt;
        IExec-&amp;gt;Enable();&lt;br /&gt;
&lt;br /&gt;
        /* Print now (printing above would have defeated a Forbid or Disable) */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Pri Address     SigAlloc    SigWait    Taskname\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        struct TaskNode *node = (struct TaskNode *)IExec-&amp;gt;GetHead(ourtasklist);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nWAITING:\n&amp;quot;);&lt;br /&gt;
        while (tnode = (struct TaskNode *)IExec-&amp;gt;GetSucc((struct Node*)node))&lt;br /&gt;
        {&lt;br /&gt;
            if(tnode == rnode)&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;\nREADY:\n&amp;quot;);  /* we set rnode above */&lt;br /&gt;
                &lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                    node-&amp;gt;tn_Node.ln_Pri, node-&amp;gt;tn_TaskAddress, node-&amp;gt;tn_SigAlloc,&lt;br /&gt;
                    node-&amp;gt;tn_SigWait, node-&amp;gt;tn_Name);&lt;br /&gt;
&lt;br /&gt;
            /* Free the memory, no need to remove the node, referenced once only */&lt;br /&gt;
            IExec-&amp;gt;FreeVec(node);&lt;br /&gt;
            node = tnode;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_LIST, ourtasklist);&lt;br /&gt;
&lt;br /&gt;
        /* Say who we are */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nTHIS TASK:\n&amp;quot;);&lt;br /&gt;
        struct Task *task = FindTask(NULL);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                task-&amp;gt;tc_Node.ln_Pri, task, task-&amp;gt;tc_SigAlloc,&lt;br /&gt;
                task-&amp;gt;tc_SigWait, task-&amp;gt;tc_Node.ln_Name);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Mutexes and Semaphores ===&lt;br /&gt;
&lt;br /&gt;
Mutexes and semaphores can be used for the purposes of mutual exclusion. With this method of locking, all tasks agree on a locking convention before accessing shared data structures. Tasks that do not require access are not affected and will run normally, so this type of exclusion is considered preferable to forbidding and disabling. This form of exclusion is explained in more detail in [[Exec_Mutexes|Mutexes]] and [[Exec_Semaphores|Semaphores]].&lt;br /&gt;
&lt;br /&gt;
== Task Exceptions ==&lt;br /&gt;
&lt;br /&gt;
Exec can provide a task with its own task-local &amp;quot;interrupt&amp;quot; called an &#039;&#039;exception&#039;&#039;. When some exceptional event occurs, an Exec exception occurs which stops a particular task from executing its normal code and forces it to execute a special, task-specific exception handling routine.&lt;br /&gt;
&lt;br /&gt;
To set up an exception routine for a task requires setting values in the task&#039;s control structure (the Task structure). The tc_ExceptCode field should point to the task&#039;s exception handling routine. If this field is zero, Exec will ignore all exceptions. The tc_ExceptData field should point to any data the exception routine needs.&lt;br /&gt;
&lt;br /&gt;
Exec exceptions work using signals. When a specific signal or signals occur, Exec will stop a task and execute its exception routine. Use the Exec function SetExcept() to tell Exec which of the task&#039;s signals should trigger the exception.&lt;br /&gt;
&lt;br /&gt;
When an exception occurs, Exec stops executing the tasks normal code and jumps immediately into the exception routine, no matter what the task was doing. The exception routine operates in the same context the task&#039;s normal code; it operates in the CPU&#039;s user mode and uses the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
Before entering the exception routine, Exec pushes the normal task code&#039;s context onto the stack. Exec then puts certain parameters in the processor registers for the exception routine to use. D0 contains a signal mask indicating which signal bit or bits caused the exception. Exec disables these signals when the task enters its exception routine. If more than one signal bit is set (i.e. if two signals occurred simultaneously), it is up to the exception routine to decide in what order to process the two different signals. A1 points to the related exception data (from tc_ExceptData), and A6 contains the Exec library base. You can think of an exception as a subtask outside of your normal task. Because task exception code executes in &#039;&#039;user&#039;&#039; mode, however, the task stack must be large enough to supply the extra space consumed during an exception.&lt;br /&gt;
&lt;br /&gt;
While processing a given exception, Exec prevents that exception from occurring recursively. At exit from your exception-processing code, you should make sure D0 contains the signal mask the exception routine received in D0 because Exec looks here to see which signals it should reactivate. When the task executes the RTS instruction at the end of the exception routine, the system restores the previous contents of all of the task registers and resumes the task at the point where it was interrupted by the exception signal.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Exceptions Are Tricky&#039;&#039;. Exceptions are difficult to use safely. An exception can interrupt a task that is executing a critical section of code within a system function, or one that has locked a system resource such as the disk or blitter (note that even simple text output uses the blitter.) This possibility makes it dangerous to use most system functions within an exception unless you are sure that your interrupted task was performing only local, non-critical operations.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Traps ==&lt;br /&gt;
&lt;br /&gt;
Task &#039;&#039;traps&#039;&#039; are synchronous exceptions to the normal flow of program control. They are always generated as a direct result of an operation performed by your program&#039;s code. Whether they are accidental or purposely generated, they will result in your program being forced into a special condition in which it must immediately handle the trap. Address error, privilege violation, zero divide, and trap instructions all result in task traps. They may be generated directly by the &#039;&#039;68000&#039;&#039; processor (Motorola calls them &amp;quot;exceptions&amp;quot;) or simulated by software.&lt;br /&gt;
&lt;br /&gt;
A task that incurs a trap has no choice but to respond immediately. The task must have a module of code to handle the trap. Your task may be aborted if a trap occurs and no means of handling it has been provided. Default trap handling code (tc_TrapCode) is provided by the OS. You may instead choose to do your own processing of traps. The tc_TrapCode field is the address of the handler that you have designed to process the trap. The tc_TrapData field is the address of the data area for use by the trap handler.&lt;br /&gt;
&lt;br /&gt;
The system&#039;s default trap handling code generally displays a Software Error Requester or Alert containing an exception number and the program counter or task address. Processor exceptions generally have numbers in the range hex 00 to 2F. The &#039;&#039;68000&#039;&#039; processor exceptions of particular interest are as follows.&lt;br /&gt;
&lt;br /&gt;
Traps (68000 Exception Vector Numbers)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 2 || Bus error || access of nonexistent memory&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Address error || long/word access of odd address (&#039;&#039;68000&#039;&#039;)&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Illegal instruction || illegal opcode (other than Axxx or Fxxx)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Zero divide || processor division by zero&lt;br /&gt;
|-&lt;br /&gt;
| 6 || CHK instruction || register bounds error trap by CHK&lt;br /&gt;
|-&lt;br /&gt;
| 7 || TRAPV instruction || overflow error trap by TRAPV&lt;br /&gt;
|-&lt;br /&gt;
| 8 || Privilege violation || user execution of supervisor opcode&lt;br /&gt;
|-&lt;br /&gt;
| 9 || Trace || status register TRACE bit trap&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Line 1010 emulator || execution of opcode beginning with $A&lt;br /&gt;
|-&lt;br /&gt;
| 11 || Line 1111 emulator || execution of opcode beginning with $F&lt;br /&gt;
|-&lt;br /&gt;
| 32-47 || Trap instructions || TRAP &#039;&#039;N&#039;&#039; instruction where &#039;&#039;N&#039;&#039; = 0 to 15&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A system alert for a processor exception may set the high bit of the longword exception number to indicate an unrecoverable error (for example $8000¬†0005 for an unrecoverable processor exception #5). System alerts with more complex numbers are generally Amiga-specific software failures. These are built from the definitions in the &amp;amp;lt;exec/alerts.h&amp;amp;gt; include file.&lt;br /&gt;
&lt;br /&gt;
The actual stack frames generated for these traps are processor-dependent. The &#039;&#039;68010&#039;&#039;, &#039;&#039;68020&#039;&#039;, and &#039;&#039;68030&#039;&#039; processors will generate a different type of stack frame than the &#039;&#039;68000&#039;&#039;. If you plan on having your program handle its own traps, you should not make assumptions about the format of the supervisor stack frame. Check the flags in the AttnFlags field of the ExecBase structure for the type of processor in use and process the stack frame accordingly.&lt;br /&gt;
&lt;br /&gt;
=== Trap Handlers ===&lt;br /&gt;
&lt;br /&gt;
For compatibility with the &#039;&#039;68000&#039;&#039;, Exec performs trap handling in supervisor mode. This means that all task switching is disabled during trap handling.&lt;br /&gt;
&lt;br /&gt;
At entry to the task&#039;s trap handler, the system stack contains a processor-dependent trap frame as defined in the &#039;&#039;68000&#039;&#039;/&#039;&#039;10&#039;&#039;/&#039;&#039;20&#039;&#039;/&#039;&#039;30&#039;&#039; manuals. A longword exception number is added to this frame. That is, when a handler gains control, the top of stack contains the exception number and the trap frame immediately follows.&lt;br /&gt;
&lt;br /&gt;
To return from trap processing, remove the exception number from the stack (note that this is the supervisor stack, not the user stack) and then perform a return from exception (RTE).&lt;br /&gt;
&lt;br /&gt;
Because trap processing takes place in supervisor mode, with task dispatching disabled, it is strongly urged that you keep trap processing as short as possible or switch back to user mode from within your trap handler. If a trap handler already exists when you add your own trap handler, it is smart to propagate any traps that you do not handle down to the previous handler. This can be done by saving the previous address from tc_TrapCode and having your handler pass control to that address if the trap which occurred is not one you wish to handle.&lt;br /&gt;
&lt;br /&gt;
The following example installs a simple trap handler which intercepts processor divide-by-zero traps, and passes on all other traps to the previous default trap code. The example has two code modules which are linked together. The trap handler code is in assembler. The C module installs the handler, demonstrates its effectiveness, then restores the previous tc_TrapCode.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* trap_c.c - Execute me to compile me with SAS C 5.10&lt;br /&gt;
LC -b0 -cfistq -v -y -j73 trap_c.c&lt;br /&gt;
Blink FROM LIB:c.o,trap_c.o,trap_a.o TO trap LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit&lt;br /&gt;
&lt;br /&gt;
trap_c.c - C module of sample integer divide-by-zero trap&lt;br /&gt;
*/&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;exec/tasks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdlib.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdio.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE&lt;br /&gt;
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */&lt;br /&gt;
int chkabort(void) {return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
extern ULONG trapa();           /* assembler trap code in trap_a.asm */&lt;br /&gt;
&lt;br /&gt;
APTR oldTrapCode;&lt;br /&gt;
ULONG countdiv0;&lt;br /&gt;
&lt;br /&gt;
void main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
    struct Task *thistask;&lt;br /&gt;
    ULONG k,j;&lt;br /&gt;
&lt;br /&gt;
    thistask = FindTask(NULL);&lt;br /&gt;
&lt;br /&gt;
    /* Save our task&#039;s current trap code pointer */&lt;br /&gt;
    oldTrapCode = thistask-&amp;amp;gt;tc_TrapCode;&lt;br /&gt;
&lt;br /&gt;
    /* Point task to our assembler trap handler code.  Ours will just count */&lt;br /&gt;
    /* divide-by-zero traps, and pass other traps on to the normal TrapCode */&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = (APTR)trapa;&lt;br /&gt;
&lt;br /&gt;
    countdiv0 = 0L;&lt;br /&gt;
&lt;br /&gt;
    for(k=0; k&amp;amp;lt;4; k++)            /* Let&#039;s divide by zero a few times */&lt;br /&gt;
       {&lt;br /&gt;
       printf(&amp;amp;quot;dividing %ld by zero... &amp;amp;quot;,k);&lt;br /&gt;
       j = k/0L;&lt;br /&gt;
       printf(&amp;amp;quot;did it\n&amp;amp;quot;);&lt;br /&gt;
       }&lt;br /&gt;
    printf(&amp;amp;quot;\nDivide by zero happened %ld times\n&amp;amp;quot;,countdiv0);&lt;br /&gt;
&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = oldTrapCode;     /* Restore old trap code */&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;* trap_a.asm - Example trap handling code (leaves D0 intact).  Entered&lt;br /&gt;
* in supervisor mode with the following on the supervisor stack:&lt;br /&gt;
*    0(sp).l = trap#&lt;br /&gt;
*    4(sp) Processor dependent exception frame&lt;br /&gt;
&lt;br /&gt;
    INCLUDE &amp;amp;quot;exec/types.i&amp;amp;quot;&lt;br /&gt;
    INCLUDE &amp;amp;quot;libraries/dos.i&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
        XDEF _trapa&lt;br /&gt;
        XREF _countdiv0&lt;br /&gt;
        XREF _oldTrapCode&lt;br /&gt;
&lt;br /&gt;
        CODE&lt;br /&gt;
_trapa:                                 ; our trap handler entry&lt;br /&gt;
        CMPI.L  #5,(SP)                 ; is this a divide by zero ?&lt;br /&gt;
        BNE.S   notdiv0                 ; no&lt;br /&gt;
        ADD.L   #1,_countdiv0           ; yes, increment our div0 count&lt;br /&gt;
endtrap:&lt;br /&gt;
        ADDQ    #4,SP                   ; remove exception number from SSP&lt;br /&gt;
        RTE                             ; return from exception&lt;br /&gt;
notdiv0:&lt;br /&gt;
        TST.L   _oldTrapCode            ; is there another trap handler ?&lt;br /&gt;
        BEQ.S   endtrap                 ; no, so we&#039;ll exit&lt;br /&gt;
        MOVE.L  _oldTrapCode,-(SP)      ; yes, go on to old TrapCode&lt;br /&gt;
        RTS                             ; jumps to old TrapCode&lt;br /&gt;
&lt;br /&gt;
        END&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Trap Instructions ===&lt;br /&gt;
&lt;br /&gt;
The TRAP instructions in the &#039;&#039;68000&#039;&#039; generate traps 32-47. Because many independent pieces of system code may desire to use these traps, the AllocTrap() and FreeTrap() functions are provided. These work in a fashion similar to that used by AllocSignal() and FreeSignal(), mentioned above.&lt;br /&gt;
&lt;br /&gt;
Allocating a trap is simply a bookkeeping job within a task. It does not affect how the system calls the trap handler; it helps coordinate who owns what traps. Exec does nothing to determine whether or not a task is prepared to handle a particular trap. It simply calls your code. It is up to your program to handle the trap.&lt;br /&gt;
&lt;br /&gt;
To allocate any trap, you can use the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(-1)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;all trap instructions are in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can select a specific trap using this code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(3)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;trap #3 is in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To free a trap, you use the FreeTrap() function passing it the trap number to be freed.&lt;br /&gt;
&lt;br /&gt;
== Processor and Cache Control ==&lt;br /&gt;
&lt;br /&gt;
Exec provides a number of to control the processor mode and, if available, the caches. All these functions work independently of the specific &#039;&#039;M68000&#039;&#039; family processor type. This enables you to write code which correctly controls the state of both the &#039;&#039;MC68000&#039;&#039; and the &#039;&#039;MC68040&#039;&#039;. Along with processor mode and cache control, functions are provided to obtain information about the condition code register (CCR) and status register (SR). No functions are provided to control a paged memory management unit (PMMU) or floating point unit (FPU).&lt;br /&gt;
&lt;br /&gt;
Processor and Cache Control Functions&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| GetCC() || Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR() || Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState() || Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor() || Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState() || Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE() || Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU() || Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl() || Global cache control.&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA() || Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA() || Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Supervisor Mode ===&lt;br /&gt;
&lt;br /&gt;
While in supervisor mode, you have complete access to all data and registers, including those used for task scheduling and exceptions, and can execute privileged instructions. In application programs, normally only task trap code is directly executed in supervisor mode, to be compatible with the &#039;&#039;MC68000&#039;&#039;. For normal applications, it should never be necessary to switch to supervisor mode itself, only indirectly through Exec function calls. Remember that task switching is disabled while in supervisor mode. If it is absolutely needed to execute code in supervisor mode, keep it as brief as possible.&lt;br /&gt;
&lt;br /&gt;
Supervisor mode can only be entered when a &#039;&#039;680x0&#039;&#039; exception occurs (an interrupt or trap). The Supervisor() function allows you to trap an exception to a specified assembly function. In this function your have full access to all registers. No registers are saved when your function is invoked. You are responsible for restoring the system to a sane state when you are done. You must return to user mode with an RTE instruction. You must not return to user mode by executing a privileged instruction which clears the supervisor bit in the status register. Refer to a manual on the &#039;&#039;M68000&#039;&#039; family of CPUs for information about supervisor mode and available privileged instructions per processor type.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;MC68000&#039;&#039; has two stacks, the user stack (USP) and supervisor stack (SSP). As of the &#039;&#039;MC68020&#039;&#039; there are two supervisor stacks, the interrupt stack pointer (ISP) and the master stack pointer (MSP). The SuperState() function allows you to enter supervisor mode with the USP used as SSP. The function returns the SSP, which will be the MSP, if an &#039;&#039;MC68020&#039;&#039; or greater is used. Returning to user mode is done with the UserState() function. This function takes the SSP as argument, which must be saved when SuperState() is called. Because of possible problems with stack size, Supervisor() is to be preferred over SuperState().&lt;br /&gt;
&lt;br /&gt;
=== Status Register ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The processor status register bits can be set or read with the SetSR() function. This function operates in supervisor mode, thus both the upper and lower byte of the SR can be read or set. Be very sure you know what you are doing when you use this function to set bits in the SR and above all never try to use this function to enter supervisor mode. Refer to the &#039;&#039;M68000 Programmers Reference Manual&#039;&#039; by Motorola Inc. for information about the definition of individual SR bits per processor type.&lt;br /&gt;
&lt;br /&gt;
=== Condition Code Register ===&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;MC68000&#039;&#039; a copy of the processor condition codes can be obtained with the MOVE SR,&amp;amp;lt;ea&amp;amp;gt; instruction. On &#039;&#039;MC68010&#039;&#039; processors and up however, the instruction MOVE CCR,&amp;amp;lt;ea&amp;amp;gt; must be used. Using the specific &#039;&#039;MC68000&#039;&#039; instruction on later processors will cause a &#039;&#039;680x0&#039;&#039; exception since it is a privileged instruction on those processors. The GetCC() function provides a processor independent way of obtaining a copy of the condition codes. For all processors there are 5 bits which can indicate the result of an integer or a system control instruction:&lt;br /&gt;
&lt;br /&gt;
* X - extend&lt;br /&gt;
* N - negative&lt;br /&gt;
* Z - zero&lt;br /&gt;
* V - overflow&lt;br /&gt;
* C - carry&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;X&#039;&#039;&#039; bit is used for multiprecision calculations. If used, it is copy of the carry bit. The other bits state the result of a processor operation.&lt;br /&gt;
&lt;br /&gt;
=== Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
As of the &#039;&#039;MC68020&#039;&#039; all processors have an instruction cache, 256 bytes on the &#039;&#039;MC68020&#039;&#039; and &#039;&#039;MC68030&#039;&#039; and 4 KBytes on a &#039;&#039;MC68040&#039;&#039;. The &#039;&#039;MC68030&#039;&#039; and &#039;&#039;MC68040&#039;&#039; have data caches as well, 256 bytes and 4 KBytes respectively. All the processors load instructions ahead of the program counter (PC), albeit it that the &#039;&#039;MC68000&#039;&#039; and &#039;&#039;MC68010&#039;&#039; only prefetch one and two words respectively. This means the CPU loads instructions ahead of the current program counter. For this reason self-modifying code is strongly discouraged. If your code modifies or decrypts itself just ahead of the program counter, the pre-fetched instructions may not match the modified instructions. If self-modifying code must be used, flushing the cache is the safest way to prevent this.&lt;br /&gt;
&lt;br /&gt;
=== DMA Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
The CachePreDMA() and CachePostDMA() functions allow you to flush the data cache before and after Direct Memory Access. Typically only DMA device drivers benefit from this. These functions take the processor type, possible MMU and cache mode into account. When no cache is available they end up doing nothing. These functions can be replaced with ones suitable for different cache hardware. Refer to the SDK for implementation specifics.&lt;br /&gt;
&lt;br /&gt;
Since DMA device drivers read and write directly to memory, they are effected by the CopyBack feature of the &#039;&#039;MC68040&#039;&#039; (explained below). Using DMA with CopyBack mode requires a cache flush. If a DMA device needs to read RAM via DMA, it must make sure that the data in the caches has been written to memory first, by calling CachePreDMA(). In case of a write to memory, the DMA device should first clear the caches with CachePreDMA(), write the data and flush the caches again with CachePostDMA().&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following chart gives a brief description of the Exec functions that control tasks. See the SDK for details about each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Exec Task Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AddTask()&lt;br /&gt;
| Add a task to the system.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTrap()&lt;br /&gt;
| Allocate a processor trap vector.&lt;br /&gt;
|-&lt;br /&gt;
| Disable()&lt;br /&gt;
| Disable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| Enable()&lt;br /&gt;
| Enable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| FindTask()&lt;br /&gt;
| Find a specific task.&lt;br /&gt;
|-&lt;br /&gt;
| Forbid()&lt;br /&gt;
| Forbid task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| FreeTrap()&lt;br /&gt;
| Release a process trap.&lt;br /&gt;
|-&lt;br /&gt;
| Permit()&lt;br /&gt;
| Permit task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| SetTaskPri()&lt;br /&gt;
| Set the priority of a task.&lt;br /&gt;
|-&lt;br /&gt;
| RemTask()&lt;br /&gt;
| Remove a task from the system.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE()&lt;br /&gt;
| Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU()&lt;br /&gt;
| Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl()&lt;br /&gt;
| Global cache control (V37).&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA()&lt;br /&gt;
| Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA()&lt;br /&gt;
| Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| GetCC()&lt;br /&gt;
| Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR()&lt;br /&gt;
| Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState()&lt;br /&gt;
| Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor()&lt;br /&gt;
| Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState()&lt;br /&gt;
| Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CreateTask()&lt;br /&gt;
| Setup and add a new task.&lt;br /&gt;
|-&lt;br /&gt;
| DeleteTask()&lt;br /&gt;
| Delete a task created with CreateTask().&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3418</id>
		<title>Exec Tasks</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3418"/>
		<updated>2012-07-10T06:14:37Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Task Exclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{NeedUpdate}}&lt;br /&gt;
== Exec Tasks ==&lt;br /&gt;
&lt;br /&gt;
One of the most powerful features of the Amiga operating system is its ability to run and manage multiple independent program tasks, providing each task with processor time based on their priority and activity. These tasks include system device drivers, background utilities, and user interface environments, as well as normal application programs. This multitasking capability is provided by the Exec library&#039;s management of task creation, termination, scheduling, event signals, traps, exceptions, and mutual exclusion.&lt;br /&gt;
&lt;br /&gt;
This section deals with Exec on a lower level than most applications programmers need and assumes you are already familiar with the Exec basics discussed in the [[Introduction_to_Exec|Introduction to Exec]].&lt;br /&gt;
&lt;br /&gt;
== Task Structure ==&lt;br /&gt;
&lt;br /&gt;
Exec maintains task context and state information in a task-control data structure. Like most Exec structures, Task structures are dynamically linked onto various task queues through the use of an embedded Exec list Node structure (see the Lists chapter). Any task can find its own task structure by calling FindTask(NULL). The C-language form of this structure is defined in the &amp;amp;lt;exec/tasks.h&amp;amp;gt; include file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Task  {&lt;br /&gt;
    struct Node tc_Node;&lt;br /&gt;
    UBYTE       tc_Flags;&lt;br /&gt;
    UBYTE       tc_State;&lt;br /&gt;
    BYTE        tc_IDNestCnt;   /* intr disabled nesting */&lt;br /&gt;
    BYTE        tc_TDNestCnt;   /* task disabled nesting */&lt;br /&gt;
    ULONG       tc_SigAlloc;    /* sigs allocated */&lt;br /&gt;
    ULONG       tc_SigWait;     /* sigs we are waiting for */&lt;br /&gt;
    ULONG       tc_SigRecvd;    /* sigs we have received */&lt;br /&gt;
    ULONG       tc_SigExcept;   /* sigs we will take excepts for */&lt;br /&gt;
    UWORD       tc_TrapAlloc;   /* traps allocated */&lt;br /&gt;
    UWORD       tc_TrapAble;    /* traps enabled */&lt;br /&gt;
    APTR        tc_ExceptData;  /* points to except data */&lt;br /&gt;
    APTR        tc_ExceptCode;  /* points to except code */&lt;br /&gt;
    APTR        tc_TrapData;    /* points to trap code */&lt;br /&gt;
    APTR        tc_TrapCode;    /* points to trap data */&lt;br /&gt;
    APTR        tc_SPReg;       /* stack pointer */&lt;br /&gt;
    APTR        tc_SPLower;     /* stack lower bound */&lt;br /&gt;
    APTR        tc_SPUpper;     /* stack upper bound + 2*/&lt;br /&gt;
    VOID      (*tc_Switch)();   /* task losing CPU */&lt;br /&gt;
    VOID      (*tc_Launch)();   /* task getting CPU */&lt;br /&gt;
    struct List tc_MemEntry;    /* allocated memory */&lt;br /&gt;
    APTR        tc_UserData;    /* per task data */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Most of these fields are not relevant for simple tasks; they are used by Exec for state and administrative purposes. A few fields, however, are provided for the advanced programs that support higher level environments (as in the case of &#039;&#039;processes&#039;&#039;) or require precise control (as in &#039;&#039;devices&#039;&#039;). The following sections explain these fields in more detail.&lt;br /&gt;
&lt;br /&gt;
== Task Creation ==&lt;br /&gt;
&lt;br /&gt;
To create a new task manually you must allocate a task structure, initialize its various fields, and then link it into Exec with a call to AddTask(). This is not the recommended way to create a task. This detailed information is only being provided for reference.&lt;br /&gt;
&lt;br /&gt;
The task structure may be allocated by calling the AllocVecTags() function with the MEMF_SHARED attribute and AVT_ClearWithValue tag.&lt;br /&gt;
&lt;br /&gt;
The Task fields that require initialization depend on how you intend to use the task. For the simplest of tasks, only a few fields must be initialized:&lt;br /&gt;
&lt;br /&gt;
; tc_Node&lt;br /&gt;
: The task list node structure. This includes the task&#039;s priority, its type, and its name (refer to [[Exec_Lists_and_Queues|Exec Lists and Queues]] chapter).&lt;br /&gt;
&lt;br /&gt;
; tc_SPLower&lt;br /&gt;
: The lower memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPUpper&lt;br /&gt;
: The upper memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPReg&lt;br /&gt;
: The initial stack pointer. Because task stacks grow &#039;&#039;downward&#039;&#039; in memory, this field is usually set to the same value as tc_SPUpper.&lt;br /&gt;
&lt;br /&gt;
Zeroing all other unused fields will cause Exec to supply the appropriate system default values. Allocating the structure with the AVT_ClearWithValue tag is an easy way to be sure that this happens.&lt;br /&gt;
&lt;br /&gt;
Once the structure has been initialized, it must be linked to Exec. This is done with a call to AddTask() in which the following parameters are specified:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
AddTask(struct Task *task, APTR initialPC, APTR finalPC )&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The task argument is a pointer to your initialized Task structure. Set initialPC to the entry point of your task code. This is the address of the first instruction the new task will execute.&lt;br /&gt;
&lt;br /&gt;
Set finalPC to the address of the finalization code for your task. This is a code section that will receive control if the initialPC routine ever performs a return. This exists to prevent your task from being launched into random memory upon an accidental return. The finalPC routine should usually perform various program-related clean-up duties and should then remove the task. If a zero is supplied for this parameter, Exec will use its default finalization code (which simply calls the RemTask() function).&lt;br /&gt;
&lt;br /&gt;
=== Task Creation With CreateTask() ===&lt;br /&gt;
&lt;br /&gt;
The recommended of creating a task is with CreateTask() or CreateTaskTags() functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
CreateTaskTags(CONST_STRPTR name, int32 priority, APTR initialPC, uint32 stacksize, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A task created with CreateTaskTags() may be removed with the DeleteTask() function, or it may simply return when it is finished. CreateTaskTags() adds a MemList to the tc_MemEntry of the task it creates, describing all memory it has allocated for the task, including the task stack and the Task structure itself. This memory will be deallocated by Exec when the task is either explicitly removed (RemTask() or DeleteTask()) or when it exits to Exec&#039;s default task removal code (RemTask()).&lt;br /&gt;
&lt;br /&gt;
Depending on the priority of a new task and the priorities of other tasks in the system, the newly added task may begin execution immediately.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Sharing Library Pointers&#039;&#039; Although in most cases it is possible for a parent task to pass a library base to a child task so the child can use that library, for some libraries, this is not possible. For this reason, the only library base sharable between tasks is Exec&#039;s library base.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Here is an example of simple task creation. In this example there is no coordination or communication between the main process and the simple task it has created. A more complex example might use named ports and messages to coordinate the activities and shutdown of two tasks. Because our task is very simple and never calls any system functions which could cause it to be signaled or awakened, we can safely remove the task at any time.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Keep This In Mind.&#039;&#039; Because the simple task&#039;s code is a function in our program, we must stop the subtask before exiting.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// simpletask.c - Uses the function CreateTaskTags() to create a simple subtask.&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/tasks.h&amp;gt;&lt;br /&gt;
#include &amp;lt;libraries/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define STACK_SIZE 16000&lt;br /&gt;
&lt;br /&gt;
uint32 sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
/* our function prototypes */&lt;br /&gt;
void simpletask(void);&lt;br /&gt;
void cleanexit(CONST_STRPTR, int32);&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
    struct Task *task = CreateTaskTags(&amp;quot;SimpleTask&amp;quot;, 0 , simpletask, STACK_SIZE,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if(task == NULL)  cleanexit(&amp;quot;Can&#039;t create task&amp;quot;, RETURN_FAIL);&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;This program initialized a variable to zero, then started a\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;separate task which is incrementing that variable right now,\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;while this program waits for you to press RETURN.\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Press RETURN now: &amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;FGetC( IDOS-&amp;gt;Input() );&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;The shared variable now equals %ld\n&amp;quot;, sharedvar);&lt;br /&gt;
&lt;br /&gt;
    /* We can simply remove the task we added because our simpletask does not make */&lt;br /&gt;
    /* any system calls which could cause it to be awakened or signalled later.    */&lt;br /&gt;
    IExec-&amp;gt;Forbid();&lt;br /&gt;
    IEXec-&amp;gt;DeleteTask(task);&lt;br /&gt;
    IExec-&amp;gt;Permit();&lt;br /&gt;
    cleanexit(&amp;quot;&amp;quot;, RETURN_OK);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void simpletask(void)&lt;br /&gt;
{&lt;br /&gt;
    while(sharedvar &amp;lt; 0x8000000) sharedvar++;&lt;br /&gt;
    /* Wait forever because main() is going to RemTask() us */&lt;br /&gt;
    IExec-&amp;gt;Wait(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void cleanexit(CONST_STRPTR s, int32 e)&lt;br /&gt;
{&lt;br /&gt;
    if(s != NULL) IDOS-&amp;gt;Printf(&amp;quot;%s\n&amp;quot;, s);&lt;br /&gt;
    exit(e);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Stack ===&lt;br /&gt;
&lt;br /&gt;
Every task requires a stack. All normal code execution occurs on this task stack. Special modes of execution (processor traps and system interrupts for example) execute on a single &#039;&#039;supervisor mode&#039;&#039; stack and do not directly affect task stacks.&lt;br /&gt;
&lt;br /&gt;
Task stacks are normally used to store local variables, subroutine return addresses, and saved register values. Additionally, when a task loses the processor, all of its current registers are preserved on this stack (with the exception of the stack pointer itself, which must be saved in the task structure).&lt;br /&gt;
&lt;br /&gt;
The amount of stack used by a task can vary widely. As a general rule of thumb, a minimum stack size of 16000 bytes should be used. If any GUI elements are involved, a minimum stack size of 80000 bytes is recommended.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Because stack-bounds checking is not provided as a service of Exec, it is important to provide enough space for your task stack. Stack overflows are always difficult to debug and may result not only in the erratic failure of your task but also in the mysterious malfunction of other Amiga subsystems.&#039;&#039;&#039; Some compilers may provide a stack-checking option.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;You Can&#039;t Always Check The Stack.&#039;&#039; Such stack-checking options generally cannot be used if part of your code will be running on the system stack (interrupts, exceptions, handlers, servers) or on a different task&#039;s stack (libraries, devices, created tasks).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When choosing your stack size, do not cut it too close. Remember that any recursive routines in your code may use varying amounts of stack, and that future versions of system routines may use additional stack variables. By dynamically allocating buffers and arrays, most application programs can be designed to function comfortably within the recommended minimum process stack size of 16000 bytes.&lt;br /&gt;
&lt;br /&gt;
=== Task Priority ===&lt;br /&gt;
&lt;br /&gt;
A task&#039;s priority indicates its importance relative to other tasks. Higher-priority tasks receive the processor before lower-priority tasks do. Task priority is stored as a signed number ranging from -128 to +127. Higher priorities are represented by more positive values; zero is considered the neutral priority. Normally, system tasks execute somewhere in the range of +20 to -20, and most application tasks execute at priority 0.&lt;br /&gt;
&lt;br /&gt;
It is not wise to needlessly raise a task&#039;s priority. Sometimes it may be necessary to carefully select a priority so that the task can properly interact with various system tasks. The SetTaskPri() Exec function is provided for this purpose.&lt;br /&gt;
&lt;br /&gt;
== Task Termination ==&lt;br /&gt;
&lt;br /&gt;
Task termination may occur as the result of a number of situations:&lt;br /&gt;
&lt;br /&gt;
* A program returning from its initialPC routine and dropping into its finalPC routine or the system default finalizer.&lt;br /&gt;
* A task trap that is too serious for a recovery action. This includes traps like processor bus error, odd address access errors, etc.&lt;br /&gt;
* A trap that is not handled by the task. For example, the task might be terminated if your code happened to encounter a processor TRAP instruction and you did not provide a trap handling routine.&lt;br /&gt;
* An explicit call to RemTask() or DeleteTask().&lt;br /&gt;
&lt;br /&gt;
Task termination involves the deallocation of system resources and the removal of the task structure from Exec. The most important part of task termination is the deallocation of system resources. A task must return all memory that it allocated for its private use, it must terminate any outstanding I/O commands, and it must close access to any system libraries or devices that it has opened.&lt;br /&gt;
&lt;br /&gt;
It is wise to adopt a strategy for task clean-up responsibility. You should decide whether resource allocation and deallocation is the duty of the creator task or the newly created task. Often it is easier and safer for the creator to handle the resource allocation and deallocation on behalf of its offspring. In such cases, before removing the child task, you must make sure it is in a safe state such as Wait(0L) and not still using a resources or waiting for an event or signal that might still occur.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitabl&amp;quot;&lt;br /&gt;
| &#039;&#039;NOTE:&#039;&#039; Certain resources, such as signals and created ports, must be allocated and deallocated by the same task that will wait on them. Also note that if your subtask code is part of your loaded program, you must not allow your program to exit before its subtasks have cleaned up their allocations, and have been either deleted or placed in a safe state such as Wait(0).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Exclusion ==&lt;br /&gt;
&lt;br /&gt;
From time to time the advanced system program may find it necessary to access global system data structures. Because these structures are shared by the system and by other tasks that execute asynchronously to your task, a task must prevent other tasks from using these structures while it is reading from or writing to them. Task exclusion method described below preventing the operating system from switching tasks by &#039;&#039;forbidding&#039;&#039; or &#039;&#039;disabling&#039;&#039;. A section of code that requires the use of either of these mechanisms to lock out access by others is termed a &#039;&#039;critical section&#039;&#039;. &#039;&#039;&#039;Use of these methods is discouraged. For arbitrating access to data between your tasks, &#039;&#039;mutexes&#039;&#039; are a superior solution (See [[Exec_Mutexes|Exec Mutexes]]). The below solution is deprecated and will be removed from the AmigaOS soon.&lt;br /&gt;
&lt;br /&gt;
=== Forbidding Task Switching ===&lt;br /&gt;
&lt;br /&gt;
Forbidding is used when a task is accessing shared structures that might also be accessed at the same time from another task. It effectively eliminates the possibility of simultaneous access by imposing &#039;&#039;non-preemptive&#039;&#039; task scheduling. &#039;&#039;&#039;This has the net effect of disabling multitasking for as long as your task remains in its running state.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
While forbidden, your task will continue running until it performs a call to Wait() or exits from the forbidden state. Interrupts will occur normally, but no new tasks will be dispatched, &#039;&#039;regardless of their priorities&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a task running in the forbidden state calls the Wait() function, directly or indirectly, it implies a temporary exit from its forbidden state. Since almost all stdio, device I/O, and file I/O functions must Wait() for I/O completion, performing such calls will cause your task to Wait(), temporarily breaking the forbid. While the task is waiting, the system will perform normally. When the task receives one of the signals it is waiting for, it will again reenter the forbidden state.&lt;br /&gt;
&lt;br /&gt;
To become forbidden, a task calls the Forbid() function. To escape, the Permit() function is used. The use of these functions may be nested with the expected affects; you will not exit the forbidden mode until you call the outermost Permit().&lt;br /&gt;
&lt;br /&gt;
As an example, the Exec task list should only be accessed when in a Forbid() state. Accessing the list without forbidding could lead to incorrect results or it could crash the entire system. To access the task list also requires the program to disable interrupts which is discussed in the next section.&lt;br /&gt;
&lt;br /&gt;
=== Disabling Tasks ===&lt;br /&gt;
&lt;br /&gt;
Disabling is similar to forbidding, but it also prevents interrupts from occurring during a critical section. Disabling is required when a task accesses structures that are shared by interrupt code. It eliminates the possibility of an interrupt accessing shared structures by preventing interrupts from occurring. Use of disabling is strongly discouraged.&lt;br /&gt;
&lt;br /&gt;
To disable interrupts you can call the Disable() function. To enable interrupts again, use the Enable() function.&lt;br /&gt;
&lt;br /&gt;
Like forbidden sections, disabled sections can be nested. To restore normal interrupt processing, an Enable() call must be made for every Disable(). Also like forbidden sections, any direct or indirect call to the Wait() function will enable interrupts until the task regains the processor.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;WARNING: It is important to realize that there is a danger in using disabled sections&#039;&#039;. Because the software on the Amiga depends heavily on its interrupts occurring in nearly real time, you cannot disable for more than a very brief instant. Disabling interrupts for more than 250 microseconds can interfere with the normal operation of vital system functions, especially serial I/O.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It is never necessary to both Disable() and Forbid(). Because disabling prevents interrupts, it also prevents preemptive task scheduling. When disable is used within an interrupt, it will have the effect of locking out all higher level interrupts (lower level interrupts are automatically disabled by the CPU). Many Exec lists can only be accessed while disabled. Suppose you want to print the names of all system tasks. You would need to access both the TaskReady and TaskWait lists from within a single disabled section. In addition, you must avoid calling system functions that would break a disable by an indirect call to Wait() (Printf() for example). In this example, the names are gathered into a list while task switching is disabled. Then task switching is enabled and the names are printed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// tasklist.c - Snapshots and prints the ExecBase task list&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/lists.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/nodes.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/execbase.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/utility.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CONST_STRPTR VersTag = &amp;quot;$VER: tasklist 53.1 (21.6.2012)&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
extern struct ExecBase *SysBase;&lt;br /&gt;
&lt;br /&gt;
/* Use extended structure to hold task information */&lt;br /&gt;
struct TaskNode {&lt;br /&gt;
    struct Node tn_Node;&lt;br /&gt;
    uint32 tn_TaskAddress;&lt;br /&gt;
    uint32 tn_SigAlloc;&lt;br /&gt;
    uint32 tn_SigWait;&lt;br /&gt;
    TEXT tn_Name[32];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    struct TaskNode *tnode = NULL;&lt;br /&gt;
    struct TaskNode *rnode = NULL;&lt;br /&gt;
&lt;br /&gt;
    /* Allocate memory for our list */&lt;br /&gt;
    struct List *ourtasklist = IExec-&amp;gt;AllocSysObjectTags(ASOT_LIST, TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ourtasklist != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        /* Make sure tasks won&#039;t switch lists or go away */&lt;br /&gt;
        IExec-&amp;gt;Disable();&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task WAIT list */&lt;br /&gt;
        struct List *exectasklist = &amp;amp;(SysBase-&amp;gt;TaskWait);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
&lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
            }&lt;br /&gt;
            else break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task READY list */&lt;br /&gt;
        exectasklist = &amp;amp;(SysBase-&amp;gt;TaskReady);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
        &lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizoeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
                if(!rnode)  rnode = tnode;  /* first READY task */&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Re-enable interrupts and taskswitching */&lt;br /&gt;
        IExec-&amp;gt;Enable();&lt;br /&gt;
&lt;br /&gt;
        /* Print now (printing above would have defeated a Forbid or Disable) */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Pri Address     SigAlloc    SigWait    Taskname\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        struct TaskNode *node = (struct TaskNode *)IExec-&amp;gt;GetHead(ourtasklist);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nWAITING:\n&amp;quot;);&lt;br /&gt;
        while (tnode = (struct TaskNode *)IExec-&amp;gt;GetSucc((struct Node*)node))&lt;br /&gt;
        {&lt;br /&gt;
            if(tnode == rnode)&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;\nREADY:\n&amp;quot;);  /* we set rnode above */&lt;br /&gt;
                &lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                    node-&amp;gt;tn_Node.ln_Pri, node-&amp;gt;tn_TaskAddress, node-&amp;gt;tn_SigAlloc,&lt;br /&gt;
                    node-&amp;gt;tn_SigWait, node-&amp;gt;tn_Name);&lt;br /&gt;
&lt;br /&gt;
            /* Free the memory, no need to remove the node, referenced once only */&lt;br /&gt;
            IExec-&amp;gt;FreeVec(node);&lt;br /&gt;
            node = tnode;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_LIST, ourtasklist);&lt;br /&gt;
&lt;br /&gt;
        /* Say who we are */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nTHIS TASK:\n&amp;quot;);&lt;br /&gt;
        struct Task *task = FindTask(NULL);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                task-&amp;gt;tc_Node.ln_Pri, task, task-&amp;gt;tc_SigAlloc,&lt;br /&gt;
                task-&amp;gt;tc_SigWait, task-&amp;gt;tc_Node.ln_Name);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Mutexes and Semaphores ===&lt;br /&gt;
&lt;br /&gt;
Mutexes and semaphores can be used for the purposes of mutual exclusion. With this method of locking, all tasks agree on a locking convention before accessing shared data structures. Tasks that do not require access are not affected and will run normally, so this type of exclusion is considered preferable to forbidding and disabling. This form of exclusion is explained in more detail in [[Exec_Mutexes|Mutexes]] and [[Exec_Semaphores|Semaphores]].&lt;br /&gt;
&lt;br /&gt;
== Task Exceptions ==&lt;br /&gt;
&lt;br /&gt;
Exec can provide a task with its own task-local &amp;quot;interrupt&amp;quot; called an &#039;&#039;exception&#039;&#039;. When some exceptional event occurs, an Exec exception occurs which stops a particular task from executing its normal code and forces it to execute a special, task-specific exception handling routine.&lt;br /&gt;
&lt;br /&gt;
To set up an exception routine for a task requires setting values in the task&#039;s control structure (the Task structure). The tc_ExceptCode field should point to the task&#039;s exception handling routine. If this field is zero, Exec will ignore all exceptions. The tc_ExceptData field should point to any data the exception routine needs.&lt;br /&gt;
&lt;br /&gt;
Exec exceptions work using signals. When a specific signal or signals occur, Exec will stop a task and execute its exception routine. Use the Exec function SetExcept() to tell Exec which of the task&#039;s signals should trigger the exception.&lt;br /&gt;
&lt;br /&gt;
When an exception occurs, Exec stops executing the tasks normal code and jumps immediately into the exception routine, no matter what the task was doing. The exception routine operates in the same context the task&#039;s normal code; it operates in the CPU&#039;s user mode and uses the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
Before entering the exception routine, Exec pushes the normal task code&#039;s context onto the stack. Exec then puts certain parameters in the processor registers for the exception routine to use. D0 contains a signal mask indicating which signal bit or bits caused the exception. Exec disables these signals when the task enters its exception routine. If more than one signal bit is set (i.e. if two signals occurred simultaneously), it is up to the exception routine to decide in what order to process the two different signals. A1 points to the related exception data (from tc_ExceptData), and A6 contains the Exec library base. You can think of an exception as a subtask outside of your normal task. Because task exception code executes in &#039;&#039;user&#039;&#039; mode, however, the task stack must be large enough to supply the extra space consumed during an exception.&lt;br /&gt;
&lt;br /&gt;
While processing a given exception, Exec prevents that exception from occurring recursively. At exit from your exception-processing code, you should make sure D0 contains the signal mask the exception routine received in D0 because Exec looks here to see which signals it should reactivate. When the task executes the RTS instruction at the end of the exception routine, the system restores the previous contents of all of the task registers and resumes the task at the point where it was interrupted by the exception signal.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Exceptions Are Tricky&#039;&#039;. Exceptions are difficult to use safely. An exception can interrupt a task that is executing a critical section of code within a system function, or one that has locked a system resource such as the disk or blitter (note that even simple text output uses the blitter.) This possibility makes it dangerous to use most system functions within an exception unless you are sure that your interrupted task was performing only local, non-critical operations.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Traps ==&lt;br /&gt;
&lt;br /&gt;
Task &#039;&#039;traps&#039;&#039; are synchronous exceptions to the normal flow of program control. They are always generated as a direct result of an operation performed by your program&#039;s code. Whether they are accidental or purposely generated, they will result in your program being forced into a special condition in which it must immediately handle the trap. Address error, privilege violation, zero divide, and trap instructions all result in task traps. They may be generated directly by the &#039;&#039;68000&#039;&#039; processor (Motorola calls them &amp;quot;exceptions&amp;quot;) or simulated by software.&lt;br /&gt;
&lt;br /&gt;
A task that incurs a trap has no choice but to respond immediately. The task must have a module of code to handle the trap. Your task may be aborted if a trap occurs and no means of handling it has been provided. Default trap handling code (tc_TrapCode) is provided by the OS. You may instead choose to do your own processing of traps. The tc_TrapCode field is the address of the handler that you have designed to process the trap. The tc_TrapData field is the address of the data area for use by the trap handler.&lt;br /&gt;
&lt;br /&gt;
The system&#039;s default trap handling code generally displays a Software Error Requester or Alert containing an exception number and the program counter or task address. Processor exceptions generally have numbers in the range hex 00 to 2F. The &#039;&#039;68000&#039;&#039; processor exceptions of particular interest are as follows.&lt;br /&gt;
&lt;br /&gt;
Traps (68000 Exception Vector Numbers)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 2 || Bus error || access of nonexistent memory&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Address error || long/word access of odd address (&#039;&#039;68000&#039;&#039;)&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Illegal instruction || illegal opcode (other than Axxx or Fxxx)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Zero divide || processor division by zero&lt;br /&gt;
|-&lt;br /&gt;
| 6 || CHK instruction || register bounds error trap by CHK&lt;br /&gt;
|-&lt;br /&gt;
| 7 || TRAPV instruction || overflow error trap by TRAPV&lt;br /&gt;
|-&lt;br /&gt;
| 8 || Privilege violation || user execution of supervisor opcode&lt;br /&gt;
|-&lt;br /&gt;
| 9 || Trace || status register TRACE bit trap&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Line 1010 emulator || execution of opcode beginning with $A&lt;br /&gt;
|-&lt;br /&gt;
| 11 || Line 1111 emulator || execution of opcode beginning with $F&lt;br /&gt;
|-&lt;br /&gt;
| 32-47 || Trap instructions || TRAP &#039;&#039;N&#039;&#039; instruction where &#039;&#039;N&#039;&#039; = 0 to 15&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A system alert for a processor exception may set the high bit of the longword exception number to indicate an unrecoverable error (for example $8000¬†0005 for an unrecoverable processor exception #5). System alerts with more complex numbers are generally Amiga-specific software failures. These are built from the definitions in the &amp;amp;lt;exec/alerts.h&amp;amp;gt; include file.&lt;br /&gt;
&lt;br /&gt;
The actual stack frames generated for these traps are processor-dependent. The &#039;&#039;68010&#039;&#039;, &#039;&#039;68020&#039;&#039;, and &#039;&#039;68030&#039;&#039; processors will generate a different type of stack frame than the &#039;&#039;68000&#039;&#039;. If you plan on having your program handle its own traps, you should not make assumptions about the format of the supervisor stack frame. Check the flags in the AttnFlags field of the ExecBase structure for the type of processor in use and process the stack frame accordingly.&lt;br /&gt;
&lt;br /&gt;
=== Trap Handlers ===&lt;br /&gt;
&lt;br /&gt;
For compatibility with the &#039;&#039;68000&#039;&#039;, Exec performs trap handling in supervisor mode. This means that all task switching is disabled during trap handling.&lt;br /&gt;
&lt;br /&gt;
At entry to the task&#039;s trap handler, the system stack contains a processor-dependent trap frame as defined in the &#039;&#039;68000&#039;&#039;/&#039;&#039;10&#039;&#039;/&#039;&#039;20&#039;&#039;/&#039;&#039;30&#039;&#039; manuals. A longword exception number is added to this frame. That is, when a handler gains control, the top of stack contains the exception number and the trap frame immediately follows.&lt;br /&gt;
&lt;br /&gt;
To return from trap processing, remove the exception number from the stack (note that this is the supervisor stack, not the user stack) and then perform a return from exception (RTE).&lt;br /&gt;
&lt;br /&gt;
Because trap processing takes place in supervisor mode, with task dispatching disabled, it is strongly urged that you keep trap processing as short as possible or switch back to user mode from within your trap handler. If a trap handler already exists when you add your own trap handler, it is smart to propagate any traps that you do not handle down to the previous handler. This can be done by saving the previous address from tc_TrapCode and having your handler pass control to that address if the trap which occurred is not one you wish to handle.&lt;br /&gt;
&lt;br /&gt;
The following example installs a simple trap handler which intercepts processor divide-by-zero traps, and passes on all other traps to the previous default trap code. The example has two code modules which are linked together. The trap handler code is in assembler. The C module installs the handler, demonstrates its effectiveness, then restores the previous tc_TrapCode.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* trap_c.c - Execute me to compile me with SAS C 5.10&lt;br /&gt;
LC -b0 -cfistq -v -y -j73 trap_c.c&lt;br /&gt;
Blink FROM LIB:c.o,trap_c.o,trap_a.o TO trap LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit&lt;br /&gt;
&lt;br /&gt;
trap_c.c - C module of sample integer divide-by-zero trap&lt;br /&gt;
*/&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;exec/tasks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdlib.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdio.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE&lt;br /&gt;
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */&lt;br /&gt;
int chkabort(void) {return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
extern ULONG trapa();           /* assembler trap code in trap_a.asm */&lt;br /&gt;
&lt;br /&gt;
APTR oldTrapCode;&lt;br /&gt;
ULONG countdiv0;&lt;br /&gt;
&lt;br /&gt;
void main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
    struct Task *thistask;&lt;br /&gt;
    ULONG k,j;&lt;br /&gt;
&lt;br /&gt;
    thistask = FindTask(NULL);&lt;br /&gt;
&lt;br /&gt;
    /* Save our task&#039;s current trap code pointer */&lt;br /&gt;
    oldTrapCode = thistask-&amp;amp;gt;tc_TrapCode;&lt;br /&gt;
&lt;br /&gt;
    /* Point task to our assembler trap handler code.  Ours will just count */&lt;br /&gt;
    /* divide-by-zero traps, and pass other traps on to the normal TrapCode */&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = (APTR)trapa;&lt;br /&gt;
&lt;br /&gt;
    countdiv0 = 0L;&lt;br /&gt;
&lt;br /&gt;
    for(k=0; k&amp;amp;lt;4; k++)            /* Let&#039;s divide by zero a few times */&lt;br /&gt;
       {&lt;br /&gt;
       printf(&amp;amp;quot;dividing %ld by zero... &amp;amp;quot;,k);&lt;br /&gt;
       j = k/0L;&lt;br /&gt;
       printf(&amp;amp;quot;did it\n&amp;amp;quot;);&lt;br /&gt;
       }&lt;br /&gt;
    printf(&amp;amp;quot;\nDivide by zero happened %ld times\n&amp;amp;quot;,countdiv0);&lt;br /&gt;
&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = oldTrapCode;     /* Restore old trap code */&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;* trap_a.asm - Example trap handling code (leaves D0 intact).  Entered&lt;br /&gt;
* in supervisor mode with the following on the supervisor stack:&lt;br /&gt;
*    0(sp).l = trap#&lt;br /&gt;
*    4(sp) Processor dependent exception frame&lt;br /&gt;
&lt;br /&gt;
    INCLUDE &amp;amp;quot;exec/types.i&amp;amp;quot;&lt;br /&gt;
    INCLUDE &amp;amp;quot;libraries/dos.i&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
        XDEF _trapa&lt;br /&gt;
        XREF _countdiv0&lt;br /&gt;
        XREF _oldTrapCode&lt;br /&gt;
&lt;br /&gt;
        CODE&lt;br /&gt;
_trapa:                                 ; our trap handler entry&lt;br /&gt;
        CMPI.L  #5,(SP)                 ; is this a divide by zero ?&lt;br /&gt;
        BNE.S   notdiv0                 ; no&lt;br /&gt;
        ADD.L   #1,_countdiv0           ; yes, increment our div0 count&lt;br /&gt;
endtrap:&lt;br /&gt;
        ADDQ    #4,SP                   ; remove exception number from SSP&lt;br /&gt;
        RTE                             ; return from exception&lt;br /&gt;
notdiv0:&lt;br /&gt;
        TST.L   _oldTrapCode            ; is there another trap handler ?&lt;br /&gt;
        BEQ.S   endtrap                 ; no, so we&#039;ll exit&lt;br /&gt;
        MOVE.L  _oldTrapCode,-(SP)      ; yes, go on to old TrapCode&lt;br /&gt;
        RTS                             ; jumps to old TrapCode&lt;br /&gt;
&lt;br /&gt;
        END&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Trap Instructions ===&lt;br /&gt;
&lt;br /&gt;
The TRAP instructions in the &#039;&#039;68000&#039;&#039; generate traps 32-47. Because many independent pieces of system code may desire to use these traps, the AllocTrap() and FreeTrap() functions are provided. These work in a fashion similar to that used by AllocSignal() and FreeSignal(), mentioned above.&lt;br /&gt;
&lt;br /&gt;
Allocating a trap is simply a bookkeeping job within a task. It does not affect how the system calls the trap handler; it helps coordinate who owns what traps. Exec does nothing to determine whether or not a task is prepared to handle a particular trap. It simply calls your code. It is up to your program to handle the trap.&lt;br /&gt;
&lt;br /&gt;
To allocate any trap, you can use the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(-1)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;all trap instructions are in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can select a specific trap using this code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(3)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;trap #3 is in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To free a trap, you use the FreeTrap() function passing it the trap number to be freed.&lt;br /&gt;
&lt;br /&gt;
== Processor and Cache Control ==&lt;br /&gt;
&lt;br /&gt;
Exec provides a number of to control the processor mode and, if available, the caches. All these functions work independently of the specific &#039;&#039;M68000&#039;&#039; family processor type. This enables you to write code which correctly controls the state of both the &#039;&#039;MC68000&#039;&#039; and the &#039;&#039;MC68040&#039;&#039;. Along with processor mode and cache control, functions are provided to obtain information about the condition code register (CCR) and status register (SR). No functions are provided to control a paged memory management unit (PMMU) or floating point unit (FPU).&lt;br /&gt;
&lt;br /&gt;
Processor and Cache Control Functions&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| GetCC() || Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR() || Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState() || Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor() || Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState() || Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE() || Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU() || Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl() || Global cache control.&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA() || Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA() || Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Supervisor Mode ===&lt;br /&gt;
&lt;br /&gt;
While in supervisor mode, you have complete access to all data and registers, including those used for task scheduling and exceptions, and can execute privileged instructions. In application programs, normally only task trap code is directly executed in supervisor mode, to be compatible with the &#039;&#039;MC68000&#039;&#039;. For normal applications, it should never be necessary to switch to supervisor mode itself, only indirectly through Exec function calls. Remember that task switching is disabled while in supervisor mode. If it is absolutely needed to execute code in supervisor mode, keep it as brief as possible.&lt;br /&gt;
&lt;br /&gt;
Supervisor mode can only be entered when a &#039;&#039;680x0&#039;&#039; exception occurs (an interrupt or trap). The Supervisor() function allows you to trap an exception to a specified assembly function. In this function your have full access to all registers. No registers are saved when your function is invoked. You are responsible for restoring the system to a sane state when you are done. You must return to user mode with an RTE instruction. You must not return to user mode by executing a privileged instruction which clears the supervisor bit in the status register. Refer to a manual on the &#039;&#039;M68000&#039;&#039; family of CPUs for information about supervisor mode and available privileged instructions per processor type.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;MC68000&#039;&#039; has two stacks, the user stack (USP) and supervisor stack (SSP). As of the &#039;&#039;MC68020&#039;&#039; there are two supervisor stacks, the interrupt stack pointer (ISP) and the master stack pointer (MSP). The SuperState() function allows you to enter supervisor mode with the USP used as SSP. The function returns the SSP, which will be the MSP, if an &#039;&#039;MC68020&#039;&#039; or greater is used. Returning to user mode is done with the UserState() function. This function takes the SSP as argument, which must be saved when SuperState() is called. Because of possible problems with stack size, Supervisor() is to be preferred over SuperState().&lt;br /&gt;
&lt;br /&gt;
=== Status Register ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The processor status register bits can be set or read with the SetSR() function. This function operates in supervisor mode, thus both the upper and lower byte of the SR can be read or set. Be very sure you know what you are doing when you use this function to set bits in the SR and above all never try to use this function to enter supervisor mode. Refer to the &#039;&#039;M68000 Programmers Reference Manual&#039;&#039; by Motorola Inc. for information about the definition of individual SR bits per processor type.&lt;br /&gt;
&lt;br /&gt;
=== Condition Code Register ===&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;MC68000&#039;&#039; a copy of the processor condition codes can be obtained with the MOVE SR,&amp;amp;lt;ea&amp;amp;gt; instruction. On &#039;&#039;MC68010&#039;&#039; processors and up however, the instruction MOVE CCR,&amp;amp;lt;ea&amp;amp;gt; must be used. Using the specific &#039;&#039;MC68000&#039;&#039; instruction on later processors will cause a &#039;&#039;680x0&#039;&#039; exception since it is a privileged instruction on those processors. The GetCC() function provides a processor independent way of obtaining a copy of the condition codes. For all processors there are 5 bits which can indicate the result of an integer or a system control instruction:&lt;br /&gt;
&lt;br /&gt;
* X - extend&lt;br /&gt;
* N - negative&lt;br /&gt;
* Z - zero&lt;br /&gt;
* V - overflow&lt;br /&gt;
* C - carry&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;X&#039;&#039;&#039; bit is used for multiprecision calculations. If used, it is copy of the carry bit. The other bits state the result of a processor operation.&lt;br /&gt;
&lt;br /&gt;
=== Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
As of the &#039;&#039;MC68020&#039;&#039; all processors have an instruction cache, 256 bytes on the &#039;&#039;MC68020&#039;&#039; and &#039;&#039;MC68030&#039;&#039; and 4 KBytes on a &#039;&#039;MC68040&#039;&#039;. The &#039;&#039;MC68030&#039;&#039; and &#039;&#039;MC68040&#039;&#039; have data caches as well, 256 bytes and 4 KBytes respectively. All the processors load instructions ahead of the program counter (PC), albeit it that the &#039;&#039;MC68000&#039;&#039; and &#039;&#039;MC68010&#039;&#039; only prefetch one and two words respectively. This means the CPU loads instructions ahead of the current program counter. For this reason self-modifying code is strongly discouraged. If your code modifies or decrypts itself just ahead of the program counter, the pre-fetched instructions may not match the modified instructions. If self-modifying code must be used, flushing the cache is the safest way to prevent this.&lt;br /&gt;
&lt;br /&gt;
=== DMA Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
The CachePreDMA() and CachePostDMA() functions allow you to flush the data cache before and after Direct Memory Access. Typically only DMA device drivers benefit from this. These functions take the processor type, possible MMU and cache mode into account. When no cache is available they end up doing nothing. These functions can be replaced with ones suitable for different cache hardware. Refer to the SDK for implementation specifics.&lt;br /&gt;
&lt;br /&gt;
Since DMA device drivers read and write directly to memory, they are effected by the CopyBack feature of the &#039;&#039;MC68040&#039;&#039; (explained below). Using DMA with CopyBack mode requires a cache flush. If a DMA device needs to read RAM via DMA, it must make sure that the data in the caches has been written to memory first, by calling CachePreDMA(). In case of a write to memory, the DMA device should first clear the caches with CachePreDMA(), write the data and flush the caches again with CachePostDMA().&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following chart gives a brief description of the Exec functions that control tasks. See the SDK for details about each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Exec Task Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AddTask()&lt;br /&gt;
| Add a task to the system.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTrap()&lt;br /&gt;
| Allocate a processor trap vector.&lt;br /&gt;
|-&lt;br /&gt;
| Disable()&lt;br /&gt;
| Disable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| Enable()&lt;br /&gt;
| Enable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| FindTask()&lt;br /&gt;
| Find a specific task.&lt;br /&gt;
|-&lt;br /&gt;
| Forbid()&lt;br /&gt;
| Forbid task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| FreeTrap()&lt;br /&gt;
| Release a process trap.&lt;br /&gt;
|-&lt;br /&gt;
| Permit()&lt;br /&gt;
| Permit task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| SetTaskPri()&lt;br /&gt;
| Set the priority of a task.&lt;br /&gt;
|-&lt;br /&gt;
| RemTask()&lt;br /&gt;
| Remove a task from the system.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE()&lt;br /&gt;
| Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU()&lt;br /&gt;
| Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl()&lt;br /&gt;
| Global cache control (V37).&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA()&lt;br /&gt;
| Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA()&lt;br /&gt;
| Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| GetCC()&lt;br /&gt;
| Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR()&lt;br /&gt;
| Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState()&lt;br /&gt;
| Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor()&lt;br /&gt;
| Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState()&lt;br /&gt;
| Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CreateTask()&lt;br /&gt;
| Setup and add a new task.&lt;br /&gt;
|-&lt;br /&gt;
| DeleteTask()&lt;br /&gt;
| Delete a task created with CreateTask().&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3417</id>
		<title>Exec Tasks</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3417"/>
		<updated>2012-07-10T06:11:49Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Task Exclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{NeedUpdate}}&lt;br /&gt;
== Exec Tasks ==&lt;br /&gt;
&lt;br /&gt;
One of the most powerful features of the Amiga operating system is its ability to run and manage multiple independent program tasks, providing each task with processor time based on their priority and activity. These tasks include system device drivers, background utilities, and user interface environments, as well as normal application programs. This multitasking capability is provided by the Exec library&#039;s management of task creation, termination, scheduling, event signals, traps, exceptions, and mutual exclusion.&lt;br /&gt;
&lt;br /&gt;
This section deals with Exec on a lower level than most applications programmers need and assumes you are already familiar with the Exec basics discussed in the [[Introduction_to_Exec|Introduction to Exec]].&lt;br /&gt;
&lt;br /&gt;
== Task Structure ==&lt;br /&gt;
&lt;br /&gt;
Exec maintains task context and state information in a task-control data structure. Like most Exec structures, Task structures are dynamically linked onto various task queues through the use of an embedded Exec list Node structure (see the Lists chapter). Any task can find its own task structure by calling FindTask(NULL). The C-language form of this structure is defined in the &amp;amp;lt;exec/tasks.h&amp;amp;gt; include file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Task  {&lt;br /&gt;
    struct Node tc_Node;&lt;br /&gt;
    UBYTE       tc_Flags;&lt;br /&gt;
    UBYTE       tc_State;&lt;br /&gt;
    BYTE        tc_IDNestCnt;   /* intr disabled nesting */&lt;br /&gt;
    BYTE        tc_TDNestCnt;   /* task disabled nesting */&lt;br /&gt;
    ULONG       tc_SigAlloc;    /* sigs allocated */&lt;br /&gt;
    ULONG       tc_SigWait;     /* sigs we are waiting for */&lt;br /&gt;
    ULONG       tc_SigRecvd;    /* sigs we have received */&lt;br /&gt;
    ULONG       tc_SigExcept;   /* sigs we will take excepts for */&lt;br /&gt;
    UWORD       tc_TrapAlloc;   /* traps allocated */&lt;br /&gt;
    UWORD       tc_TrapAble;    /* traps enabled */&lt;br /&gt;
    APTR        tc_ExceptData;  /* points to except data */&lt;br /&gt;
    APTR        tc_ExceptCode;  /* points to except code */&lt;br /&gt;
    APTR        tc_TrapData;    /* points to trap code */&lt;br /&gt;
    APTR        tc_TrapCode;    /* points to trap data */&lt;br /&gt;
    APTR        tc_SPReg;       /* stack pointer */&lt;br /&gt;
    APTR        tc_SPLower;     /* stack lower bound */&lt;br /&gt;
    APTR        tc_SPUpper;     /* stack upper bound + 2*/&lt;br /&gt;
    VOID      (*tc_Switch)();   /* task losing CPU */&lt;br /&gt;
    VOID      (*tc_Launch)();   /* task getting CPU */&lt;br /&gt;
    struct List tc_MemEntry;    /* allocated memory */&lt;br /&gt;
    APTR        tc_UserData;    /* per task data */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Most of these fields are not relevant for simple tasks; they are used by Exec for state and administrative purposes. A few fields, however, are provided for the advanced programs that support higher level environments (as in the case of &#039;&#039;processes&#039;&#039;) or require precise control (as in &#039;&#039;devices&#039;&#039;). The following sections explain these fields in more detail.&lt;br /&gt;
&lt;br /&gt;
== Task Creation ==&lt;br /&gt;
&lt;br /&gt;
To create a new task manually you must allocate a task structure, initialize its various fields, and then link it into Exec with a call to AddTask(). This is not the recommended way to create a task. This detailed information is only being provided for reference.&lt;br /&gt;
&lt;br /&gt;
The task structure may be allocated by calling the AllocVecTags() function with the MEMF_SHARED attribute and AVT_ClearWithValue tag.&lt;br /&gt;
&lt;br /&gt;
The Task fields that require initialization depend on how you intend to use the task. For the simplest of tasks, only a few fields must be initialized:&lt;br /&gt;
&lt;br /&gt;
; tc_Node&lt;br /&gt;
: The task list node structure. This includes the task&#039;s priority, its type, and its name (refer to [[Exec_Lists_and_Queues|Exec Lists and Queues]] chapter).&lt;br /&gt;
&lt;br /&gt;
; tc_SPLower&lt;br /&gt;
: The lower memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPUpper&lt;br /&gt;
: The upper memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPReg&lt;br /&gt;
: The initial stack pointer. Because task stacks grow &#039;&#039;downward&#039;&#039; in memory, this field is usually set to the same value as tc_SPUpper.&lt;br /&gt;
&lt;br /&gt;
Zeroing all other unused fields will cause Exec to supply the appropriate system default values. Allocating the structure with the AVT_ClearWithValue tag is an easy way to be sure that this happens.&lt;br /&gt;
&lt;br /&gt;
Once the structure has been initialized, it must be linked to Exec. This is done with a call to AddTask() in which the following parameters are specified:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
AddTask(struct Task *task, APTR initialPC, APTR finalPC )&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The task argument is a pointer to your initialized Task structure. Set initialPC to the entry point of your task code. This is the address of the first instruction the new task will execute.&lt;br /&gt;
&lt;br /&gt;
Set finalPC to the address of the finalization code for your task. This is a code section that will receive control if the initialPC routine ever performs a return. This exists to prevent your task from being launched into random memory upon an accidental return. The finalPC routine should usually perform various program-related clean-up duties and should then remove the task. If a zero is supplied for this parameter, Exec will use its default finalization code (which simply calls the RemTask() function).&lt;br /&gt;
&lt;br /&gt;
=== Task Creation With CreateTask() ===&lt;br /&gt;
&lt;br /&gt;
The recommended of creating a task is with CreateTask() or CreateTaskTags() functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
CreateTaskTags(CONST_STRPTR name, int32 priority, APTR initialPC, uint32 stacksize, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A task created with CreateTaskTags() may be removed with the DeleteTask() function, or it may simply return when it is finished. CreateTaskTags() adds a MemList to the tc_MemEntry of the task it creates, describing all memory it has allocated for the task, including the task stack and the Task structure itself. This memory will be deallocated by Exec when the task is either explicitly removed (RemTask() or DeleteTask()) or when it exits to Exec&#039;s default task removal code (RemTask()).&lt;br /&gt;
&lt;br /&gt;
Depending on the priority of a new task and the priorities of other tasks in the system, the newly added task may begin execution immediately.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Sharing Library Pointers&#039;&#039; Although in most cases it is possible for a parent task to pass a library base to a child task so the child can use that library, for some libraries, this is not possible. For this reason, the only library base sharable between tasks is Exec&#039;s library base.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Here is an example of simple task creation. In this example there is no coordination or communication between the main process and the simple task it has created. A more complex example might use named ports and messages to coordinate the activities and shutdown of two tasks. Because our task is very simple and never calls any system functions which could cause it to be signaled or awakened, we can safely remove the task at any time.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Keep This In Mind.&#039;&#039; Because the simple task&#039;s code is a function in our program, we must stop the subtask before exiting.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// simpletask.c - Uses the function CreateTaskTags() to create a simple subtask.&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/tasks.h&amp;gt;&lt;br /&gt;
#include &amp;lt;libraries/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define STACK_SIZE 16000&lt;br /&gt;
&lt;br /&gt;
uint32 sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
/* our function prototypes */&lt;br /&gt;
void simpletask(void);&lt;br /&gt;
void cleanexit(CONST_STRPTR, int32);&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
    struct Task *task = CreateTaskTags(&amp;quot;SimpleTask&amp;quot;, 0 , simpletask, STACK_SIZE,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if(task == NULL)  cleanexit(&amp;quot;Can&#039;t create task&amp;quot;, RETURN_FAIL);&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;This program initialized a variable to zero, then started a\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;separate task which is incrementing that variable right now,\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;while this program waits for you to press RETURN.\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Press RETURN now: &amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;FGetC( IDOS-&amp;gt;Input() );&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;The shared variable now equals %ld\n&amp;quot;, sharedvar);&lt;br /&gt;
&lt;br /&gt;
    /* We can simply remove the task we added because our simpletask does not make */&lt;br /&gt;
    /* any system calls which could cause it to be awakened or signalled later.    */&lt;br /&gt;
    IExec-&amp;gt;Forbid();&lt;br /&gt;
    IEXec-&amp;gt;DeleteTask(task);&lt;br /&gt;
    IExec-&amp;gt;Permit();&lt;br /&gt;
    cleanexit(&amp;quot;&amp;quot;, RETURN_OK);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void simpletask(void)&lt;br /&gt;
{&lt;br /&gt;
    while(sharedvar &amp;lt; 0x8000000) sharedvar++;&lt;br /&gt;
    /* Wait forever because main() is going to RemTask() us */&lt;br /&gt;
    IExec-&amp;gt;Wait(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void cleanexit(CONST_STRPTR s, int32 e)&lt;br /&gt;
{&lt;br /&gt;
    if(s != NULL) IDOS-&amp;gt;Printf(&amp;quot;%s\n&amp;quot;, s);&lt;br /&gt;
    exit(e);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Stack ===&lt;br /&gt;
&lt;br /&gt;
Every task requires a stack. All normal code execution occurs on this task stack. Special modes of execution (processor traps and system interrupts for example) execute on a single &#039;&#039;supervisor mode&#039;&#039; stack and do not directly affect task stacks.&lt;br /&gt;
&lt;br /&gt;
Task stacks are normally used to store local variables, subroutine return addresses, and saved register values. Additionally, when a task loses the processor, all of its current registers are preserved on this stack (with the exception of the stack pointer itself, which must be saved in the task structure).&lt;br /&gt;
&lt;br /&gt;
The amount of stack used by a task can vary widely. As a general rule of thumb, a minimum stack size of 16000 bytes should be used. If any GUI elements are involved, a minimum stack size of 80000 bytes is recommended.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Because stack-bounds checking is not provided as a service of Exec, it is important to provide enough space for your task stack. Stack overflows are always difficult to debug and may result not only in the erratic failure of your task but also in the mysterious malfunction of other Amiga subsystems.&#039;&#039;&#039; Some compilers may provide a stack-checking option.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;You Can&#039;t Always Check The Stack.&#039;&#039; Such stack-checking options generally cannot be used if part of your code will be running on the system stack (interrupts, exceptions, handlers, servers) or on a different task&#039;s stack (libraries, devices, created tasks).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When choosing your stack size, do not cut it too close. Remember that any recursive routines in your code may use varying amounts of stack, and that future versions of system routines may use additional stack variables. By dynamically allocating buffers and arrays, most application programs can be designed to function comfortably within the recommended minimum process stack size of 16000 bytes.&lt;br /&gt;
&lt;br /&gt;
=== Task Priority ===&lt;br /&gt;
&lt;br /&gt;
A task&#039;s priority indicates its importance relative to other tasks. Higher-priority tasks receive the processor before lower-priority tasks do. Task priority is stored as a signed number ranging from -128 to +127. Higher priorities are represented by more positive values; zero is considered the neutral priority. Normally, system tasks execute somewhere in the range of +20 to -20, and most application tasks execute at priority 0.&lt;br /&gt;
&lt;br /&gt;
It is not wise to needlessly raise a task&#039;s priority. Sometimes it may be necessary to carefully select a priority so that the task can properly interact with various system tasks. The SetTaskPri() Exec function is provided for this purpose.&lt;br /&gt;
&lt;br /&gt;
== Task Termination ==&lt;br /&gt;
&lt;br /&gt;
Task termination may occur as the result of a number of situations:&lt;br /&gt;
&lt;br /&gt;
* A program returning from its initialPC routine and dropping into its finalPC routine or the system default finalizer.&lt;br /&gt;
* A task trap that is too serious for a recovery action. This includes traps like processor bus error, odd address access errors, etc.&lt;br /&gt;
* A trap that is not handled by the task. For example, the task might be terminated if your code happened to encounter a processor TRAP instruction and you did not provide a trap handling routine.&lt;br /&gt;
* An explicit call to RemTask() or DeleteTask().&lt;br /&gt;
&lt;br /&gt;
Task termination involves the deallocation of system resources and the removal of the task structure from Exec. The most important part of task termination is the deallocation of system resources. A task must return all memory that it allocated for its private use, it must terminate any outstanding I/O commands, and it must close access to any system libraries or devices that it has opened.&lt;br /&gt;
&lt;br /&gt;
It is wise to adopt a strategy for task clean-up responsibility. You should decide whether resource allocation and deallocation is the duty of the creator task or the newly created task. Often it is easier and safer for the creator to handle the resource allocation and deallocation on behalf of its offspring. In such cases, before removing the child task, you must make sure it is in a safe state such as Wait(0L) and not still using a resources or waiting for an event or signal that might still occur.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitabl&amp;quot;&lt;br /&gt;
| &#039;&#039;NOTE:&#039;&#039; Certain resources, such as signals and created ports, must be allocated and deallocated by the same task that will wait on them. Also note that if your subtask code is part of your loaded program, you must not allow your program to exit before its subtasks have cleaned up their allocations, and have been either deleted or placed in a safe state such as Wait(0).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Exclusion ==&lt;br /&gt;
&lt;br /&gt;
From time to time the advanced system program may find it necessary to access global system data structures. Because these structures are shared by the system and by other tasks that execute asynchronously to your task, a task must prevent other tasks from using these structures while it is reading from or writing to them. This can be accomplished by preventing the operating system from switching tasks by &#039;&#039;forbidding&#039;&#039; or &#039;&#039;disabling&#039;&#039;. A section of code that requires the use of either of these mechanisms to lock out access by others is termed a &#039;&#039;critical section&#039;&#039;. &#039;&#039;&#039;Use of these methods is discouraged. For arbitrating access to data between your tasks, &#039;&#039;mutexes&#039;&#039; are a superior solution (See [[Exec_Mutexes|Exec Mutexes]]). The below solution is deprecated and will be removed from the AmigaOS soon.&lt;br /&gt;
&lt;br /&gt;
=== Forbidding Task Switching ===&lt;br /&gt;
&lt;br /&gt;
Forbidding is used when a task is accessing shared structures that might also be accessed at the same time from another task. It effectively eliminates the possibility of simultaneous access by imposing &#039;&#039;non-preemptive&#039;&#039; task scheduling. &#039;&#039;&#039;This has the net effect of disabling multitasking for as long as your task remains in its running state.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
While forbidden, your task will continue running until it performs a call to Wait() or exits from the forbidden state. Interrupts will occur normally, but no new tasks will be dispatched, &#039;&#039;regardless of their priorities&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a task running in the forbidden state calls the Wait() function, directly or indirectly, it implies a temporary exit from its forbidden state. Since almost all stdio, device I/O, and file I/O functions must Wait() for I/O completion, performing such calls will cause your task to Wait(), temporarily breaking the forbid. While the task is waiting, the system will perform normally. When the task receives one of the signals it is waiting for, it will again reenter the forbidden state.&lt;br /&gt;
&lt;br /&gt;
To become forbidden, a task calls the Forbid() function. To escape, the Permit() function is used. The use of these functions may be nested with the expected affects; you will not exit the forbidden mode until you call the outermost Permit().&lt;br /&gt;
&lt;br /&gt;
As an example, the Exec task list should only be accessed when in a Forbid() state. Accessing the list without forbidding could lead to incorrect results or it could crash the entire system. To access the task list also requires the program to disable interrupts which is discussed in the next section.&lt;br /&gt;
&lt;br /&gt;
=== Disabling Tasks ===&lt;br /&gt;
&lt;br /&gt;
Disabling is similar to forbidding, but it also prevents interrupts from occurring during a critical section. Disabling is required when a task accesses structures that are shared by interrupt code. It eliminates the possibility of an interrupt accessing shared structures by preventing interrupts from occurring. Use of disabling is strongly discouraged.&lt;br /&gt;
&lt;br /&gt;
To disable interrupts you can call the Disable() function. To enable interrupts again, use the Enable() function.&lt;br /&gt;
&lt;br /&gt;
Like forbidden sections, disabled sections can be nested. To restore normal interrupt processing, an Enable() call must be made for every Disable(). Also like forbidden sections, any direct or indirect call to the Wait() function will enable interrupts until the task regains the processor.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;WARNING: It is important to realize that there is a danger in using disabled sections&#039;&#039;. Because the software on the Amiga depends heavily on its interrupts occurring in nearly real time, you cannot disable for more than a very brief instant. Disabling interrupts for more than 250 microseconds can interfere with the normal operation of vital system functions, especially serial I/O.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It is never necessary to both Disable() and Forbid(). Because disabling prevents interrupts, it also prevents preemptive task scheduling. When disable is used within an interrupt, it will have the effect of locking out all higher level interrupts (lower level interrupts are automatically disabled by the CPU). Many Exec lists can only be accessed while disabled. Suppose you want to print the names of all system tasks. You would need to access both the TaskReady and TaskWait lists from within a single disabled section. In addition, you must avoid calling system functions that would break a disable by an indirect call to Wait() (Printf() for example). In this example, the names are gathered into a list while task switching is disabled. Then task switching is enabled and the names are printed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// tasklist.c - Snapshots and prints the ExecBase task list&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/lists.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/nodes.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/execbase.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/utility.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CONST_STRPTR VersTag = &amp;quot;$VER: tasklist 53.1 (21.6.2012)&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
extern struct ExecBase *SysBase;&lt;br /&gt;
&lt;br /&gt;
/* Use extended structure to hold task information */&lt;br /&gt;
struct TaskNode {&lt;br /&gt;
    struct Node tn_Node;&lt;br /&gt;
    uint32 tn_TaskAddress;&lt;br /&gt;
    uint32 tn_SigAlloc;&lt;br /&gt;
    uint32 tn_SigWait;&lt;br /&gt;
    TEXT tn_Name[32];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    struct TaskNode *tnode = NULL;&lt;br /&gt;
    struct TaskNode *rnode = NULL;&lt;br /&gt;
&lt;br /&gt;
    /* Allocate memory for our list */&lt;br /&gt;
    struct List *ourtasklist = IExec-&amp;gt;AllocSysObjectTags(ASOT_LIST, TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ourtasklist != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        /* Make sure tasks won&#039;t switch lists or go away */&lt;br /&gt;
        IExec-&amp;gt;Disable();&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task WAIT list */&lt;br /&gt;
        struct List *exectasklist = &amp;amp;(SysBase-&amp;gt;TaskWait);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
&lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
            }&lt;br /&gt;
            else break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task READY list */&lt;br /&gt;
        exectasklist = &amp;amp;(SysBase-&amp;gt;TaskReady);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
        &lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizoeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
                if(!rnode)  rnode = tnode;  /* first READY task */&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Re-enable interrupts and taskswitching */&lt;br /&gt;
        IExec-&amp;gt;Enable();&lt;br /&gt;
&lt;br /&gt;
        /* Print now (printing above would have defeated a Forbid or Disable) */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Pri Address     SigAlloc    SigWait    Taskname\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        struct TaskNode *node = (struct TaskNode *)IExec-&amp;gt;GetHead(ourtasklist);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nWAITING:\n&amp;quot;);&lt;br /&gt;
        while (tnode = (struct TaskNode *)IExec-&amp;gt;GetSucc((struct Node*)node))&lt;br /&gt;
        {&lt;br /&gt;
            if(tnode == rnode)&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;\nREADY:\n&amp;quot;);  /* we set rnode above */&lt;br /&gt;
                &lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                    node-&amp;gt;tn_Node.ln_Pri, node-&amp;gt;tn_TaskAddress, node-&amp;gt;tn_SigAlloc,&lt;br /&gt;
                    node-&amp;gt;tn_SigWait, node-&amp;gt;tn_Name);&lt;br /&gt;
&lt;br /&gt;
            /* Free the memory, no need to remove the node, referenced once only */&lt;br /&gt;
            IExec-&amp;gt;FreeVec(node);&lt;br /&gt;
            node = tnode;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_LIST, ourtasklist);&lt;br /&gt;
&lt;br /&gt;
        /* Say who we are */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nTHIS TASK:\n&amp;quot;);&lt;br /&gt;
        struct Task *task = FindTask(NULL);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                task-&amp;gt;tc_Node.ln_Pri, task, task-&amp;gt;tc_SigAlloc,&lt;br /&gt;
                task-&amp;gt;tc_SigWait, task-&amp;gt;tc_Node.ln_Name);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Mutexes and Semaphores ===&lt;br /&gt;
&lt;br /&gt;
Mutexes and semaphores can be used for the purposes of mutual exclusion. With this method of locking, all tasks agree on a locking convention before accessing shared data structures. Tasks that do not require access are not affected and will run normally, so this type of exclusion is considered preferable to forbidding and disabling. This form of exclusion is explained in more detail in [[Exec_Mutexes|Mutexes]] and [[Exec_Semaphores|Semaphores]].&lt;br /&gt;
&lt;br /&gt;
== Task Exceptions ==&lt;br /&gt;
&lt;br /&gt;
Exec can provide a task with its own task-local &amp;quot;interrupt&amp;quot; called an &#039;&#039;exception&#039;&#039;. When some exceptional event occurs, an Exec exception occurs which stops a particular task from executing its normal code and forces it to execute a special, task-specific exception handling routine.&lt;br /&gt;
&lt;br /&gt;
To set up an exception routine for a task requires setting values in the task&#039;s control structure (the Task structure). The tc_ExceptCode field should point to the task&#039;s exception handling routine. If this field is zero, Exec will ignore all exceptions. The tc_ExceptData field should point to any data the exception routine needs.&lt;br /&gt;
&lt;br /&gt;
Exec exceptions work using signals. When a specific signal or signals occur, Exec will stop a task and execute its exception routine. Use the Exec function SetExcept() to tell Exec which of the task&#039;s signals should trigger the exception.&lt;br /&gt;
&lt;br /&gt;
When an exception occurs, Exec stops executing the tasks normal code and jumps immediately into the exception routine, no matter what the task was doing. The exception routine operates in the same context the task&#039;s normal code; it operates in the CPU&#039;s user mode and uses the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
Before entering the exception routine, Exec pushes the normal task code&#039;s context onto the stack. Exec then puts certain parameters in the processor registers for the exception routine to use. D0 contains a signal mask indicating which signal bit or bits caused the exception. Exec disables these signals when the task enters its exception routine. If more than one signal bit is set (i.e. if two signals occurred simultaneously), it is up to the exception routine to decide in what order to process the two different signals. A1 points to the related exception data (from tc_ExceptData), and A6 contains the Exec library base. You can think of an exception as a subtask outside of your normal task. Because task exception code executes in &#039;&#039;user&#039;&#039; mode, however, the task stack must be large enough to supply the extra space consumed during an exception.&lt;br /&gt;
&lt;br /&gt;
While processing a given exception, Exec prevents that exception from occurring recursively. At exit from your exception-processing code, you should make sure D0 contains the signal mask the exception routine received in D0 because Exec looks here to see which signals it should reactivate. When the task executes the RTS instruction at the end of the exception routine, the system restores the previous contents of all of the task registers and resumes the task at the point where it was interrupted by the exception signal.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Exceptions Are Tricky&#039;&#039;. Exceptions are difficult to use safely. An exception can interrupt a task that is executing a critical section of code within a system function, or one that has locked a system resource such as the disk or blitter (note that even simple text output uses the blitter.) This possibility makes it dangerous to use most system functions within an exception unless you are sure that your interrupted task was performing only local, non-critical operations.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Traps ==&lt;br /&gt;
&lt;br /&gt;
Task &#039;&#039;traps&#039;&#039; are synchronous exceptions to the normal flow of program control. They are always generated as a direct result of an operation performed by your program&#039;s code. Whether they are accidental or purposely generated, they will result in your program being forced into a special condition in which it must immediately handle the trap. Address error, privilege violation, zero divide, and trap instructions all result in task traps. They may be generated directly by the &#039;&#039;68000&#039;&#039; processor (Motorola calls them &amp;quot;exceptions&amp;quot;) or simulated by software.&lt;br /&gt;
&lt;br /&gt;
A task that incurs a trap has no choice but to respond immediately. The task must have a module of code to handle the trap. Your task may be aborted if a trap occurs and no means of handling it has been provided. Default trap handling code (tc_TrapCode) is provided by the OS. You may instead choose to do your own processing of traps. The tc_TrapCode field is the address of the handler that you have designed to process the trap. The tc_TrapData field is the address of the data area for use by the trap handler.&lt;br /&gt;
&lt;br /&gt;
The system&#039;s default trap handling code generally displays a Software Error Requester or Alert containing an exception number and the program counter or task address. Processor exceptions generally have numbers in the range hex 00 to 2F. The &#039;&#039;68000&#039;&#039; processor exceptions of particular interest are as follows.&lt;br /&gt;
&lt;br /&gt;
Traps (68000 Exception Vector Numbers)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 2 || Bus error || access of nonexistent memory&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Address error || long/word access of odd address (&#039;&#039;68000&#039;&#039;)&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Illegal instruction || illegal opcode (other than Axxx or Fxxx)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Zero divide || processor division by zero&lt;br /&gt;
|-&lt;br /&gt;
| 6 || CHK instruction || register bounds error trap by CHK&lt;br /&gt;
|-&lt;br /&gt;
| 7 || TRAPV instruction || overflow error trap by TRAPV&lt;br /&gt;
|-&lt;br /&gt;
| 8 || Privilege violation || user execution of supervisor opcode&lt;br /&gt;
|-&lt;br /&gt;
| 9 || Trace || status register TRACE bit trap&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Line 1010 emulator || execution of opcode beginning with $A&lt;br /&gt;
|-&lt;br /&gt;
| 11 || Line 1111 emulator || execution of opcode beginning with $F&lt;br /&gt;
|-&lt;br /&gt;
| 32-47 || Trap instructions || TRAP &#039;&#039;N&#039;&#039; instruction where &#039;&#039;N&#039;&#039; = 0 to 15&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A system alert for a processor exception may set the high bit of the longword exception number to indicate an unrecoverable error (for example $8000¬†0005 for an unrecoverable processor exception #5). System alerts with more complex numbers are generally Amiga-specific software failures. These are built from the definitions in the &amp;amp;lt;exec/alerts.h&amp;amp;gt; include file.&lt;br /&gt;
&lt;br /&gt;
The actual stack frames generated for these traps are processor-dependent. The &#039;&#039;68010&#039;&#039;, &#039;&#039;68020&#039;&#039;, and &#039;&#039;68030&#039;&#039; processors will generate a different type of stack frame than the &#039;&#039;68000&#039;&#039;. If you plan on having your program handle its own traps, you should not make assumptions about the format of the supervisor stack frame. Check the flags in the AttnFlags field of the ExecBase structure for the type of processor in use and process the stack frame accordingly.&lt;br /&gt;
&lt;br /&gt;
=== Trap Handlers ===&lt;br /&gt;
&lt;br /&gt;
For compatibility with the &#039;&#039;68000&#039;&#039;, Exec performs trap handling in supervisor mode. This means that all task switching is disabled during trap handling.&lt;br /&gt;
&lt;br /&gt;
At entry to the task&#039;s trap handler, the system stack contains a processor-dependent trap frame as defined in the &#039;&#039;68000&#039;&#039;/&#039;&#039;10&#039;&#039;/&#039;&#039;20&#039;&#039;/&#039;&#039;30&#039;&#039; manuals. A longword exception number is added to this frame. That is, when a handler gains control, the top of stack contains the exception number and the trap frame immediately follows.&lt;br /&gt;
&lt;br /&gt;
To return from trap processing, remove the exception number from the stack (note that this is the supervisor stack, not the user stack) and then perform a return from exception (RTE).&lt;br /&gt;
&lt;br /&gt;
Because trap processing takes place in supervisor mode, with task dispatching disabled, it is strongly urged that you keep trap processing as short as possible or switch back to user mode from within your trap handler. If a trap handler already exists when you add your own trap handler, it is smart to propagate any traps that you do not handle down to the previous handler. This can be done by saving the previous address from tc_TrapCode and having your handler pass control to that address if the trap which occurred is not one you wish to handle.&lt;br /&gt;
&lt;br /&gt;
The following example installs a simple trap handler which intercepts processor divide-by-zero traps, and passes on all other traps to the previous default trap code. The example has two code modules which are linked together. The trap handler code is in assembler. The C module installs the handler, demonstrates its effectiveness, then restores the previous tc_TrapCode.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* trap_c.c - Execute me to compile me with SAS C 5.10&lt;br /&gt;
LC -b0 -cfistq -v -y -j73 trap_c.c&lt;br /&gt;
Blink FROM LIB:c.o,trap_c.o,trap_a.o TO trap LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit&lt;br /&gt;
&lt;br /&gt;
trap_c.c - C module of sample integer divide-by-zero trap&lt;br /&gt;
*/&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;exec/tasks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdlib.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdio.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE&lt;br /&gt;
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */&lt;br /&gt;
int chkabort(void) {return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
extern ULONG trapa();           /* assembler trap code in trap_a.asm */&lt;br /&gt;
&lt;br /&gt;
APTR oldTrapCode;&lt;br /&gt;
ULONG countdiv0;&lt;br /&gt;
&lt;br /&gt;
void main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
    struct Task *thistask;&lt;br /&gt;
    ULONG k,j;&lt;br /&gt;
&lt;br /&gt;
    thistask = FindTask(NULL);&lt;br /&gt;
&lt;br /&gt;
    /* Save our task&#039;s current trap code pointer */&lt;br /&gt;
    oldTrapCode = thistask-&amp;amp;gt;tc_TrapCode;&lt;br /&gt;
&lt;br /&gt;
    /* Point task to our assembler trap handler code.  Ours will just count */&lt;br /&gt;
    /* divide-by-zero traps, and pass other traps on to the normal TrapCode */&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = (APTR)trapa;&lt;br /&gt;
&lt;br /&gt;
    countdiv0 = 0L;&lt;br /&gt;
&lt;br /&gt;
    for(k=0; k&amp;amp;lt;4; k++)            /* Let&#039;s divide by zero a few times */&lt;br /&gt;
       {&lt;br /&gt;
       printf(&amp;amp;quot;dividing %ld by zero... &amp;amp;quot;,k);&lt;br /&gt;
       j = k/0L;&lt;br /&gt;
       printf(&amp;amp;quot;did it\n&amp;amp;quot;);&lt;br /&gt;
       }&lt;br /&gt;
    printf(&amp;amp;quot;\nDivide by zero happened %ld times\n&amp;amp;quot;,countdiv0);&lt;br /&gt;
&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = oldTrapCode;     /* Restore old trap code */&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;* trap_a.asm - Example trap handling code (leaves D0 intact).  Entered&lt;br /&gt;
* in supervisor mode with the following on the supervisor stack:&lt;br /&gt;
*    0(sp).l = trap#&lt;br /&gt;
*    4(sp) Processor dependent exception frame&lt;br /&gt;
&lt;br /&gt;
    INCLUDE &amp;amp;quot;exec/types.i&amp;amp;quot;&lt;br /&gt;
    INCLUDE &amp;amp;quot;libraries/dos.i&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
        XDEF _trapa&lt;br /&gt;
        XREF _countdiv0&lt;br /&gt;
        XREF _oldTrapCode&lt;br /&gt;
&lt;br /&gt;
        CODE&lt;br /&gt;
_trapa:                                 ; our trap handler entry&lt;br /&gt;
        CMPI.L  #5,(SP)                 ; is this a divide by zero ?&lt;br /&gt;
        BNE.S   notdiv0                 ; no&lt;br /&gt;
        ADD.L   #1,_countdiv0           ; yes, increment our div0 count&lt;br /&gt;
endtrap:&lt;br /&gt;
        ADDQ    #4,SP                   ; remove exception number from SSP&lt;br /&gt;
        RTE                             ; return from exception&lt;br /&gt;
notdiv0:&lt;br /&gt;
        TST.L   _oldTrapCode            ; is there another trap handler ?&lt;br /&gt;
        BEQ.S   endtrap                 ; no, so we&#039;ll exit&lt;br /&gt;
        MOVE.L  _oldTrapCode,-(SP)      ; yes, go on to old TrapCode&lt;br /&gt;
        RTS                             ; jumps to old TrapCode&lt;br /&gt;
&lt;br /&gt;
        END&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Trap Instructions ===&lt;br /&gt;
&lt;br /&gt;
The TRAP instructions in the &#039;&#039;68000&#039;&#039; generate traps 32-47. Because many independent pieces of system code may desire to use these traps, the AllocTrap() and FreeTrap() functions are provided. These work in a fashion similar to that used by AllocSignal() and FreeSignal(), mentioned above.&lt;br /&gt;
&lt;br /&gt;
Allocating a trap is simply a bookkeeping job within a task. It does not affect how the system calls the trap handler; it helps coordinate who owns what traps. Exec does nothing to determine whether or not a task is prepared to handle a particular trap. It simply calls your code. It is up to your program to handle the trap.&lt;br /&gt;
&lt;br /&gt;
To allocate any trap, you can use the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(-1)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;all trap instructions are in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can select a specific trap using this code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(3)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;trap #3 is in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To free a trap, you use the FreeTrap() function passing it the trap number to be freed.&lt;br /&gt;
&lt;br /&gt;
== Processor and Cache Control ==&lt;br /&gt;
&lt;br /&gt;
Exec provides a number of to control the processor mode and, if available, the caches. All these functions work independently of the specific &#039;&#039;M68000&#039;&#039; family processor type. This enables you to write code which correctly controls the state of both the &#039;&#039;MC68000&#039;&#039; and the &#039;&#039;MC68040&#039;&#039;. Along with processor mode and cache control, functions are provided to obtain information about the condition code register (CCR) and status register (SR). No functions are provided to control a paged memory management unit (PMMU) or floating point unit (FPU).&lt;br /&gt;
&lt;br /&gt;
Processor and Cache Control Functions&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| GetCC() || Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR() || Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState() || Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor() || Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState() || Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE() || Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU() || Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl() || Global cache control.&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA() || Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA() || Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Supervisor Mode ===&lt;br /&gt;
&lt;br /&gt;
While in supervisor mode, you have complete access to all data and registers, including those used for task scheduling and exceptions, and can execute privileged instructions. In application programs, normally only task trap code is directly executed in supervisor mode, to be compatible with the &#039;&#039;MC68000&#039;&#039;. For normal applications, it should never be necessary to switch to supervisor mode itself, only indirectly through Exec function calls. Remember that task switching is disabled while in supervisor mode. If it is absolutely needed to execute code in supervisor mode, keep it as brief as possible.&lt;br /&gt;
&lt;br /&gt;
Supervisor mode can only be entered when a &#039;&#039;680x0&#039;&#039; exception occurs (an interrupt or trap). The Supervisor() function allows you to trap an exception to a specified assembly function. In this function your have full access to all registers. No registers are saved when your function is invoked. You are responsible for restoring the system to a sane state when you are done. You must return to user mode with an RTE instruction. You must not return to user mode by executing a privileged instruction which clears the supervisor bit in the status register. Refer to a manual on the &#039;&#039;M68000&#039;&#039; family of CPUs for information about supervisor mode and available privileged instructions per processor type.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;MC68000&#039;&#039; has two stacks, the user stack (USP) and supervisor stack (SSP). As of the &#039;&#039;MC68020&#039;&#039; there are two supervisor stacks, the interrupt stack pointer (ISP) and the master stack pointer (MSP). The SuperState() function allows you to enter supervisor mode with the USP used as SSP. The function returns the SSP, which will be the MSP, if an &#039;&#039;MC68020&#039;&#039; or greater is used. Returning to user mode is done with the UserState() function. This function takes the SSP as argument, which must be saved when SuperState() is called. Because of possible problems with stack size, Supervisor() is to be preferred over SuperState().&lt;br /&gt;
&lt;br /&gt;
=== Status Register ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The processor status register bits can be set or read with the SetSR() function. This function operates in supervisor mode, thus both the upper and lower byte of the SR can be read or set. Be very sure you know what you are doing when you use this function to set bits in the SR and above all never try to use this function to enter supervisor mode. Refer to the &#039;&#039;M68000 Programmers Reference Manual&#039;&#039; by Motorola Inc. for information about the definition of individual SR bits per processor type.&lt;br /&gt;
&lt;br /&gt;
=== Condition Code Register ===&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;MC68000&#039;&#039; a copy of the processor condition codes can be obtained with the MOVE SR,&amp;amp;lt;ea&amp;amp;gt; instruction. On &#039;&#039;MC68010&#039;&#039; processors and up however, the instruction MOVE CCR,&amp;amp;lt;ea&amp;amp;gt; must be used. Using the specific &#039;&#039;MC68000&#039;&#039; instruction on later processors will cause a &#039;&#039;680x0&#039;&#039; exception since it is a privileged instruction on those processors. The GetCC() function provides a processor independent way of obtaining a copy of the condition codes. For all processors there are 5 bits which can indicate the result of an integer or a system control instruction:&lt;br /&gt;
&lt;br /&gt;
* X - extend&lt;br /&gt;
* N - negative&lt;br /&gt;
* Z - zero&lt;br /&gt;
* V - overflow&lt;br /&gt;
* C - carry&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;X&#039;&#039;&#039; bit is used for multiprecision calculations. If used, it is copy of the carry bit. The other bits state the result of a processor operation.&lt;br /&gt;
&lt;br /&gt;
=== Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
As of the &#039;&#039;MC68020&#039;&#039; all processors have an instruction cache, 256 bytes on the &#039;&#039;MC68020&#039;&#039; and &#039;&#039;MC68030&#039;&#039; and 4 KBytes on a &#039;&#039;MC68040&#039;&#039;. The &#039;&#039;MC68030&#039;&#039; and &#039;&#039;MC68040&#039;&#039; have data caches as well, 256 bytes and 4 KBytes respectively. All the processors load instructions ahead of the program counter (PC), albeit it that the &#039;&#039;MC68000&#039;&#039; and &#039;&#039;MC68010&#039;&#039; only prefetch one and two words respectively. This means the CPU loads instructions ahead of the current program counter. For this reason self-modifying code is strongly discouraged. If your code modifies or decrypts itself just ahead of the program counter, the pre-fetched instructions may not match the modified instructions. If self-modifying code must be used, flushing the cache is the safest way to prevent this.&lt;br /&gt;
&lt;br /&gt;
=== DMA Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
The CachePreDMA() and CachePostDMA() functions allow you to flush the data cache before and after Direct Memory Access. Typically only DMA device drivers benefit from this. These functions take the processor type, possible MMU and cache mode into account. When no cache is available they end up doing nothing. These functions can be replaced with ones suitable for different cache hardware. Refer to the SDK for implementation specifics.&lt;br /&gt;
&lt;br /&gt;
Since DMA device drivers read and write directly to memory, they are effected by the CopyBack feature of the &#039;&#039;MC68040&#039;&#039; (explained below). Using DMA with CopyBack mode requires a cache flush. If a DMA device needs to read RAM via DMA, it must make sure that the data in the caches has been written to memory first, by calling CachePreDMA(). In case of a write to memory, the DMA device should first clear the caches with CachePreDMA(), write the data and flush the caches again with CachePostDMA().&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following chart gives a brief description of the Exec functions that control tasks. See the SDK for details about each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Exec Task Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AddTask()&lt;br /&gt;
| Add a task to the system.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTrap()&lt;br /&gt;
| Allocate a processor trap vector.&lt;br /&gt;
|-&lt;br /&gt;
| Disable()&lt;br /&gt;
| Disable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| Enable()&lt;br /&gt;
| Enable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| FindTask()&lt;br /&gt;
| Find a specific task.&lt;br /&gt;
|-&lt;br /&gt;
| Forbid()&lt;br /&gt;
| Forbid task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| FreeTrap()&lt;br /&gt;
| Release a process trap.&lt;br /&gt;
|-&lt;br /&gt;
| Permit()&lt;br /&gt;
| Permit task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| SetTaskPri()&lt;br /&gt;
| Set the priority of a task.&lt;br /&gt;
|-&lt;br /&gt;
| RemTask()&lt;br /&gt;
| Remove a task from the system.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE()&lt;br /&gt;
| Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU()&lt;br /&gt;
| Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl()&lt;br /&gt;
| Global cache control (V37).&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA()&lt;br /&gt;
| Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA()&lt;br /&gt;
| Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| GetCC()&lt;br /&gt;
| Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR()&lt;br /&gt;
| Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState()&lt;br /&gt;
| Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor()&lt;br /&gt;
| Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState()&lt;br /&gt;
| Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CreateTask()&lt;br /&gt;
| Setup and add a new task.&lt;br /&gt;
|-&lt;br /&gt;
| DeleteTask()&lt;br /&gt;
| Delete a task created with CreateTask().&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3416</id>
		<title>Exec Tasks</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3416"/>
		<updated>2012-07-10T06:08:09Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Forbidding Task Switching */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{NeedUpdate}}&lt;br /&gt;
== Exec Tasks ==&lt;br /&gt;
&lt;br /&gt;
One of the most powerful features of the Amiga operating system is its ability to run and manage multiple independent program tasks, providing each task with processor time based on their priority and activity. These tasks include system device drivers, background utilities, and user interface environments, as well as normal application programs. This multitasking capability is provided by the Exec library&#039;s management of task creation, termination, scheduling, event signals, traps, exceptions, and mutual exclusion.&lt;br /&gt;
&lt;br /&gt;
This section deals with Exec on a lower level than most applications programmers need and assumes you are already familiar with the Exec basics discussed in the [[Introduction_to_Exec|Introduction to Exec]].&lt;br /&gt;
&lt;br /&gt;
== Task Structure ==&lt;br /&gt;
&lt;br /&gt;
Exec maintains task context and state information in a task-control data structure. Like most Exec structures, Task structures are dynamically linked onto various task queues through the use of an embedded Exec list Node structure (see the Lists chapter). Any task can find its own task structure by calling FindTask(NULL). The C-language form of this structure is defined in the &amp;amp;lt;exec/tasks.h&amp;amp;gt; include file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Task  {&lt;br /&gt;
    struct Node tc_Node;&lt;br /&gt;
    UBYTE       tc_Flags;&lt;br /&gt;
    UBYTE       tc_State;&lt;br /&gt;
    BYTE        tc_IDNestCnt;   /* intr disabled nesting */&lt;br /&gt;
    BYTE        tc_TDNestCnt;   /* task disabled nesting */&lt;br /&gt;
    ULONG       tc_SigAlloc;    /* sigs allocated */&lt;br /&gt;
    ULONG       tc_SigWait;     /* sigs we are waiting for */&lt;br /&gt;
    ULONG       tc_SigRecvd;    /* sigs we have received */&lt;br /&gt;
    ULONG       tc_SigExcept;   /* sigs we will take excepts for */&lt;br /&gt;
    UWORD       tc_TrapAlloc;   /* traps allocated */&lt;br /&gt;
    UWORD       tc_TrapAble;    /* traps enabled */&lt;br /&gt;
    APTR        tc_ExceptData;  /* points to except data */&lt;br /&gt;
    APTR        tc_ExceptCode;  /* points to except code */&lt;br /&gt;
    APTR        tc_TrapData;    /* points to trap code */&lt;br /&gt;
    APTR        tc_TrapCode;    /* points to trap data */&lt;br /&gt;
    APTR        tc_SPReg;       /* stack pointer */&lt;br /&gt;
    APTR        tc_SPLower;     /* stack lower bound */&lt;br /&gt;
    APTR        tc_SPUpper;     /* stack upper bound + 2*/&lt;br /&gt;
    VOID      (*tc_Switch)();   /* task losing CPU */&lt;br /&gt;
    VOID      (*tc_Launch)();   /* task getting CPU */&lt;br /&gt;
    struct List tc_MemEntry;    /* allocated memory */&lt;br /&gt;
    APTR        tc_UserData;    /* per task data */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Most of these fields are not relevant for simple tasks; they are used by Exec for state and administrative purposes. A few fields, however, are provided for the advanced programs that support higher level environments (as in the case of &#039;&#039;processes&#039;&#039;) or require precise control (as in &#039;&#039;devices&#039;&#039;). The following sections explain these fields in more detail.&lt;br /&gt;
&lt;br /&gt;
== Task Creation ==&lt;br /&gt;
&lt;br /&gt;
To create a new task manually you must allocate a task structure, initialize its various fields, and then link it into Exec with a call to AddTask(). This is not the recommended way to create a task. This detailed information is only being provided for reference.&lt;br /&gt;
&lt;br /&gt;
The task structure may be allocated by calling the AllocVecTags() function with the MEMF_SHARED attribute and AVT_ClearWithValue tag.&lt;br /&gt;
&lt;br /&gt;
The Task fields that require initialization depend on how you intend to use the task. For the simplest of tasks, only a few fields must be initialized:&lt;br /&gt;
&lt;br /&gt;
; tc_Node&lt;br /&gt;
: The task list node structure. This includes the task&#039;s priority, its type, and its name (refer to [[Exec_Lists_and_Queues|Exec Lists and Queues]] chapter).&lt;br /&gt;
&lt;br /&gt;
; tc_SPLower&lt;br /&gt;
: The lower memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPUpper&lt;br /&gt;
: The upper memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPReg&lt;br /&gt;
: The initial stack pointer. Because task stacks grow &#039;&#039;downward&#039;&#039; in memory, this field is usually set to the same value as tc_SPUpper.&lt;br /&gt;
&lt;br /&gt;
Zeroing all other unused fields will cause Exec to supply the appropriate system default values. Allocating the structure with the AVT_ClearWithValue tag is an easy way to be sure that this happens.&lt;br /&gt;
&lt;br /&gt;
Once the structure has been initialized, it must be linked to Exec. This is done with a call to AddTask() in which the following parameters are specified:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
AddTask(struct Task *task, APTR initialPC, APTR finalPC )&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The task argument is a pointer to your initialized Task structure. Set initialPC to the entry point of your task code. This is the address of the first instruction the new task will execute.&lt;br /&gt;
&lt;br /&gt;
Set finalPC to the address of the finalization code for your task. This is a code section that will receive control if the initialPC routine ever performs a return. This exists to prevent your task from being launched into random memory upon an accidental return. The finalPC routine should usually perform various program-related clean-up duties and should then remove the task. If a zero is supplied for this parameter, Exec will use its default finalization code (which simply calls the RemTask() function).&lt;br /&gt;
&lt;br /&gt;
=== Task Creation With CreateTask() ===&lt;br /&gt;
&lt;br /&gt;
The recommended of creating a task is with CreateTask() or CreateTaskTags() functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
CreateTaskTags(CONST_STRPTR name, int32 priority, APTR initialPC, uint32 stacksize, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A task created with CreateTaskTags() may be removed with the DeleteTask() function, or it may simply return when it is finished. CreateTaskTags() adds a MemList to the tc_MemEntry of the task it creates, describing all memory it has allocated for the task, including the task stack and the Task structure itself. This memory will be deallocated by Exec when the task is either explicitly removed (RemTask() or DeleteTask()) or when it exits to Exec&#039;s default task removal code (RemTask()).&lt;br /&gt;
&lt;br /&gt;
Depending on the priority of a new task and the priorities of other tasks in the system, the newly added task may begin execution immediately.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Sharing Library Pointers&#039;&#039; Although in most cases it is possible for a parent task to pass a library base to a child task so the child can use that library, for some libraries, this is not possible. For this reason, the only library base sharable between tasks is Exec&#039;s library base.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Here is an example of simple task creation. In this example there is no coordination or communication between the main process and the simple task it has created. A more complex example might use named ports and messages to coordinate the activities and shutdown of two tasks. Because our task is very simple and never calls any system functions which could cause it to be signaled or awakened, we can safely remove the task at any time.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Keep This In Mind.&#039;&#039; Because the simple task&#039;s code is a function in our program, we must stop the subtask before exiting.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// simpletask.c - Uses the function CreateTaskTags() to create a simple subtask.&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/tasks.h&amp;gt;&lt;br /&gt;
#include &amp;lt;libraries/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define STACK_SIZE 16000&lt;br /&gt;
&lt;br /&gt;
uint32 sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
/* our function prototypes */&lt;br /&gt;
void simpletask(void);&lt;br /&gt;
void cleanexit(CONST_STRPTR, int32);&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
    struct Task *task = CreateTaskTags(&amp;quot;SimpleTask&amp;quot;, 0 , simpletask, STACK_SIZE,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if(task == NULL)  cleanexit(&amp;quot;Can&#039;t create task&amp;quot;, RETURN_FAIL);&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;This program initialized a variable to zero, then started a\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;separate task which is incrementing that variable right now,\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;while this program waits for you to press RETURN.\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Press RETURN now: &amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;FGetC( IDOS-&amp;gt;Input() );&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;The shared variable now equals %ld\n&amp;quot;, sharedvar);&lt;br /&gt;
&lt;br /&gt;
    /* We can simply remove the task we added because our simpletask does not make */&lt;br /&gt;
    /* any system calls which could cause it to be awakened or signalled later.    */&lt;br /&gt;
    IExec-&amp;gt;Forbid();&lt;br /&gt;
    IEXec-&amp;gt;DeleteTask(task);&lt;br /&gt;
    IExec-&amp;gt;Permit();&lt;br /&gt;
    cleanexit(&amp;quot;&amp;quot;, RETURN_OK);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void simpletask(void)&lt;br /&gt;
{&lt;br /&gt;
    while(sharedvar &amp;lt; 0x8000000) sharedvar++;&lt;br /&gt;
    /* Wait forever because main() is going to RemTask() us */&lt;br /&gt;
    IExec-&amp;gt;Wait(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void cleanexit(CONST_STRPTR s, int32 e)&lt;br /&gt;
{&lt;br /&gt;
    if(s != NULL) IDOS-&amp;gt;Printf(&amp;quot;%s\n&amp;quot;, s);&lt;br /&gt;
    exit(e);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Stack ===&lt;br /&gt;
&lt;br /&gt;
Every task requires a stack. All normal code execution occurs on this task stack. Special modes of execution (processor traps and system interrupts for example) execute on a single &#039;&#039;supervisor mode&#039;&#039; stack and do not directly affect task stacks.&lt;br /&gt;
&lt;br /&gt;
Task stacks are normally used to store local variables, subroutine return addresses, and saved register values. Additionally, when a task loses the processor, all of its current registers are preserved on this stack (with the exception of the stack pointer itself, which must be saved in the task structure).&lt;br /&gt;
&lt;br /&gt;
The amount of stack used by a task can vary widely. As a general rule of thumb, a minimum stack size of 16000 bytes should be used. If any GUI elements are involved, a minimum stack size of 80000 bytes is recommended.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Because stack-bounds checking is not provided as a service of Exec, it is important to provide enough space for your task stack. Stack overflows are always difficult to debug and may result not only in the erratic failure of your task but also in the mysterious malfunction of other Amiga subsystems.&#039;&#039;&#039; Some compilers may provide a stack-checking option.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;You Can&#039;t Always Check The Stack.&#039;&#039; Such stack-checking options generally cannot be used if part of your code will be running on the system stack (interrupts, exceptions, handlers, servers) or on a different task&#039;s stack (libraries, devices, created tasks).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When choosing your stack size, do not cut it too close. Remember that any recursive routines in your code may use varying amounts of stack, and that future versions of system routines may use additional stack variables. By dynamically allocating buffers and arrays, most application programs can be designed to function comfortably within the recommended minimum process stack size of 16000 bytes.&lt;br /&gt;
&lt;br /&gt;
=== Task Priority ===&lt;br /&gt;
&lt;br /&gt;
A task&#039;s priority indicates its importance relative to other tasks. Higher-priority tasks receive the processor before lower-priority tasks do. Task priority is stored as a signed number ranging from -128 to +127. Higher priorities are represented by more positive values; zero is considered the neutral priority. Normally, system tasks execute somewhere in the range of +20 to -20, and most application tasks execute at priority 0.&lt;br /&gt;
&lt;br /&gt;
It is not wise to needlessly raise a task&#039;s priority. Sometimes it may be necessary to carefully select a priority so that the task can properly interact with various system tasks. The SetTaskPri() Exec function is provided for this purpose.&lt;br /&gt;
&lt;br /&gt;
== Task Termination ==&lt;br /&gt;
&lt;br /&gt;
Task termination may occur as the result of a number of situations:&lt;br /&gt;
&lt;br /&gt;
* A program returning from its initialPC routine and dropping into its finalPC routine or the system default finalizer.&lt;br /&gt;
* A task trap that is too serious for a recovery action. This includes traps like processor bus error, odd address access errors, etc.&lt;br /&gt;
* A trap that is not handled by the task. For example, the task might be terminated if your code happened to encounter a processor TRAP instruction and you did not provide a trap handling routine.&lt;br /&gt;
* An explicit call to RemTask() or DeleteTask().&lt;br /&gt;
&lt;br /&gt;
Task termination involves the deallocation of system resources and the removal of the task structure from Exec. The most important part of task termination is the deallocation of system resources. A task must return all memory that it allocated for its private use, it must terminate any outstanding I/O commands, and it must close access to any system libraries or devices that it has opened.&lt;br /&gt;
&lt;br /&gt;
It is wise to adopt a strategy for task clean-up responsibility. You should decide whether resource allocation and deallocation is the duty of the creator task or the newly created task. Often it is easier and safer for the creator to handle the resource allocation and deallocation on behalf of its offspring. In such cases, before removing the child task, you must make sure it is in a safe state such as Wait(0L) and not still using a resources or waiting for an event or signal that might still occur.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitabl&amp;quot;&lt;br /&gt;
| &#039;&#039;NOTE:&#039;&#039; Certain resources, such as signals and created ports, must be allocated and deallocated by the same task that will wait on them. Also note that if your subtask code is part of your loaded program, you must not allow your program to exit before its subtasks have cleaned up their allocations, and have been either deleted or placed in a safe state such as Wait(0).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Exclusion ==&lt;br /&gt;
&lt;br /&gt;
From time to time the advanced system program may find it necessary to access global system data structures. Because these structures are shared by the system and by other tasks that execute asynchronously to your task, a task must prevent other tasks from using these structures while it is reading from or writing to them. This can be accomplished by preventing the operating system from switching tasks by &#039;&#039;forbidding&#039;&#039; or &#039;&#039;disabling&#039;&#039;. A section of code that requires the use of either of these mechanisms to lock out access by others is termed a &#039;&#039;critical section&#039;&#039;. Use of these methods is discouraged. For arbitrating access to data between your tasks, &#039;&#039;mutexes&#039;&#039; are a superior solution. (See [[Exec_Mutexes|Exec Mutexes]])&lt;br /&gt;
&lt;br /&gt;
=== Forbidding Task Switching ===&lt;br /&gt;
&lt;br /&gt;
Forbidding is used when a task is accessing shared structures that might also be accessed at the same time from another task. It effectively eliminates the possibility of simultaneous access by imposing &#039;&#039;non-preemptive&#039;&#039; task scheduling. &#039;&#039;&#039;This has the net effect of disabling multitasking for as long as your task remains in its running state.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
While forbidden, your task will continue running until it performs a call to Wait() or exits from the forbidden state. Interrupts will occur normally, but no new tasks will be dispatched, &#039;&#039;regardless of their priorities&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a task running in the forbidden state calls the Wait() function, directly or indirectly, it implies a temporary exit from its forbidden state. Since almost all stdio, device I/O, and file I/O functions must Wait() for I/O completion, performing such calls will cause your task to Wait(), temporarily breaking the forbid. While the task is waiting, the system will perform normally. When the task receives one of the signals it is waiting for, it will again reenter the forbidden state.&lt;br /&gt;
&lt;br /&gt;
To become forbidden, a task calls the Forbid() function. To escape, the Permit() function is used. The use of these functions may be nested with the expected affects; you will not exit the forbidden mode until you call the outermost Permit().&lt;br /&gt;
&lt;br /&gt;
As an example, the Exec task list should only be accessed when in a Forbid() state. Accessing the list without forbidding could lead to incorrect results or it could crash the entire system. To access the task list also requires the program to disable interrupts which is discussed in the next section.&lt;br /&gt;
&lt;br /&gt;
=== Disabling Tasks ===&lt;br /&gt;
&lt;br /&gt;
Disabling is similar to forbidding, but it also prevents interrupts from occurring during a critical section. Disabling is required when a task accesses structures that are shared by interrupt code. It eliminates the possibility of an interrupt accessing shared structures by preventing interrupts from occurring. Use of disabling is strongly discouraged.&lt;br /&gt;
&lt;br /&gt;
To disable interrupts you can call the Disable() function. To enable interrupts again, use the Enable() function.&lt;br /&gt;
&lt;br /&gt;
Like forbidden sections, disabled sections can be nested. To restore normal interrupt processing, an Enable() call must be made for every Disable(). Also like forbidden sections, any direct or indirect call to the Wait() function will enable interrupts until the task regains the processor.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;WARNING: It is important to realize that there is a danger in using disabled sections&#039;&#039;. Because the software on the Amiga depends heavily on its interrupts occurring in nearly real time, you cannot disable for more than a very brief instant. Disabling interrupts for more than 250 microseconds can interfere with the normal operation of vital system functions, especially serial I/O.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It is never necessary to both Disable() and Forbid(). Because disabling prevents interrupts, it also prevents preemptive task scheduling. When disable is used within an interrupt, it will have the effect of locking out all higher level interrupts (lower level interrupts are automatically disabled by the CPU). Many Exec lists can only be accessed while disabled. Suppose you want to print the names of all system tasks. You would need to access both the TaskReady and TaskWait lists from within a single disabled section. In addition, you must avoid calling system functions that would break a disable by an indirect call to Wait() (Printf() for example). In this example, the names are gathered into a list while task switching is disabled. Then task switching is enabled and the names are printed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// tasklist.c - Snapshots and prints the ExecBase task list&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/lists.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/nodes.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/execbase.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/utility.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CONST_STRPTR VersTag = &amp;quot;$VER: tasklist 53.1 (21.6.2012)&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
extern struct ExecBase *SysBase;&lt;br /&gt;
&lt;br /&gt;
/* Use extended structure to hold task information */&lt;br /&gt;
struct TaskNode {&lt;br /&gt;
    struct Node tn_Node;&lt;br /&gt;
    uint32 tn_TaskAddress;&lt;br /&gt;
    uint32 tn_SigAlloc;&lt;br /&gt;
    uint32 tn_SigWait;&lt;br /&gt;
    TEXT tn_Name[32];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    struct TaskNode *tnode = NULL;&lt;br /&gt;
    struct TaskNode *rnode = NULL;&lt;br /&gt;
&lt;br /&gt;
    /* Allocate memory for our list */&lt;br /&gt;
    struct List *ourtasklist = IExec-&amp;gt;AllocSysObjectTags(ASOT_LIST, TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ourtasklist != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        /* Make sure tasks won&#039;t switch lists or go away */&lt;br /&gt;
        IExec-&amp;gt;Disable();&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task WAIT list */&lt;br /&gt;
        struct List *exectasklist = &amp;amp;(SysBase-&amp;gt;TaskWait);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
&lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
            }&lt;br /&gt;
            else break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task READY list */&lt;br /&gt;
        exectasklist = &amp;amp;(SysBase-&amp;gt;TaskReady);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
        &lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizoeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
                if(!rnode)  rnode = tnode;  /* first READY task */&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Re-enable interrupts and taskswitching */&lt;br /&gt;
        IExec-&amp;gt;Enable();&lt;br /&gt;
&lt;br /&gt;
        /* Print now (printing above would have defeated a Forbid or Disable) */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Pri Address     SigAlloc    SigWait    Taskname\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        struct TaskNode *node = (struct TaskNode *)IExec-&amp;gt;GetHead(ourtasklist);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nWAITING:\n&amp;quot;);&lt;br /&gt;
        while (tnode = (struct TaskNode *)IExec-&amp;gt;GetSucc((struct Node*)node))&lt;br /&gt;
        {&lt;br /&gt;
            if(tnode == rnode)&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;\nREADY:\n&amp;quot;);  /* we set rnode above */&lt;br /&gt;
                &lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                    node-&amp;gt;tn_Node.ln_Pri, node-&amp;gt;tn_TaskAddress, node-&amp;gt;tn_SigAlloc,&lt;br /&gt;
                    node-&amp;gt;tn_SigWait, node-&amp;gt;tn_Name);&lt;br /&gt;
&lt;br /&gt;
            /* Free the memory, no need to remove the node, referenced once only */&lt;br /&gt;
            IExec-&amp;gt;FreeVec(node);&lt;br /&gt;
            node = tnode;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_LIST, ourtasklist);&lt;br /&gt;
&lt;br /&gt;
        /* Say who we are */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nTHIS TASK:\n&amp;quot;);&lt;br /&gt;
        struct Task *task = FindTask(NULL);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                task-&amp;gt;tc_Node.ln_Pri, task, task-&amp;gt;tc_SigAlloc,&lt;br /&gt;
                task-&amp;gt;tc_SigWait, task-&amp;gt;tc_Node.ln_Name);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Mutexes and Semaphores ===&lt;br /&gt;
&lt;br /&gt;
Mutexes and semaphores can be used for the purposes of mutual exclusion. With this method of locking, all tasks agree on a locking convention before accessing shared data structures. Tasks that do not require access are not affected and will run normally, so this type of exclusion is considered preferable to forbidding and disabling. This form of exclusion is explained in more detail in [[Exec_Mutexes|Mutexes]] and [[Exec_Semaphores|Semaphores]].&lt;br /&gt;
&lt;br /&gt;
== Task Exceptions ==&lt;br /&gt;
&lt;br /&gt;
Exec can provide a task with its own task-local &amp;quot;interrupt&amp;quot; called an &#039;&#039;exception&#039;&#039;. When some exceptional event occurs, an Exec exception occurs which stops a particular task from executing its normal code and forces it to execute a special, task-specific exception handling routine.&lt;br /&gt;
&lt;br /&gt;
To set up an exception routine for a task requires setting values in the task&#039;s control structure (the Task structure). The tc_ExceptCode field should point to the task&#039;s exception handling routine. If this field is zero, Exec will ignore all exceptions. The tc_ExceptData field should point to any data the exception routine needs.&lt;br /&gt;
&lt;br /&gt;
Exec exceptions work using signals. When a specific signal or signals occur, Exec will stop a task and execute its exception routine. Use the Exec function SetExcept() to tell Exec which of the task&#039;s signals should trigger the exception.&lt;br /&gt;
&lt;br /&gt;
When an exception occurs, Exec stops executing the tasks normal code and jumps immediately into the exception routine, no matter what the task was doing. The exception routine operates in the same context the task&#039;s normal code; it operates in the CPU&#039;s user mode and uses the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
Before entering the exception routine, Exec pushes the normal task code&#039;s context onto the stack. Exec then puts certain parameters in the processor registers for the exception routine to use. D0 contains a signal mask indicating which signal bit or bits caused the exception. Exec disables these signals when the task enters its exception routine. If more than one signal bit is set (i.e. if two signals occurred simultaneously), it is up to the exception routine to decide in what order to process the two different signals. A1 points to the related exception data (from tc_ExceptData), and A6 contains the Exec library base. You can think of an exception as a subtask outside of your normal task. Because task exception code executes in &#039;&#039;user&#039;&#039; mode, however, the task stack must be large enough to supply the extra space consumed during an exception.&lt;br /&gt;
&lt;br /&gt;
While processing a given exception, Exec prevents that exception from occurring recursively. At exit from your exception-processing code, you should make sure D0 contains the signal mask the exception routine received in D0 because Exec looks here to see which signals it should reactivate. When the task executes the RTS instruction at the end of the exception routine, the system restores the previous contents of all of the task registers and resumes the task at the point where it was interrupted by the exception signal.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Exceptions Are Tricky&#039;&#039;. Exceptions are difficult to use safely. An exception can interrupt a task that is executing a critical section of code within a system function, or one that has locked a system resource such as the disk or blitter (note that even simple text output uses the blitter.) This possibility makes it dangerous to use most system functions within an exception unless you are sure that your interrupted task was performing only local, non-critical operations.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Traps ==&lt;br /&gt;
&lt;br /&gt;
Task &#039;&#039;traps&#039;&#039; are synchronous exceptions to the normal flow of program control. They are always generated as a direct result of an operation performed by your program&#039;s code. Whether they are accidental or purposely generated, they will result in your program being forced into a special condition in which it must immediately handle the trap. Address error, privilege violation, zero divide, and trap instructions all result in task traps. They may be generated directly by the &#039;&#039;68000&#039;&#039; processor (Motorola calls them &amp;quot;exceptions&amp;quot;) or simulated by software.&lt;br /&gt;
&lt;br /&gt;
A task that incurs a trap has no choice but to respond immediately. The task must have a module of code to handle the trap. Your task may be aborted if a trap occurs and no means of handling it has been provided. Default trap handling code (tc_TrapCode) is provided by the OS. You may instead choose to do your own processing of traps. The tc_TrapCode field is the address of the handler that you have designed to process the trap. The tc_TrapData field is the address of the data area for use by the trap handler.&lt;br /&gt;
&lt;br /&gt;
The system&#039;s default trap handling code generally displays a Software Error Requester or Alert containing an exception number and the program counter or task address. Processor exceptions generally have numbers in the range hex 00 to 2F. The &#039;&#039;68000&#039;&#039; processor exceptions of particular interest are as follows.&lt;br /&gt;
&lt;br /&gt;
Traps (68000 Exception Vector Numbers)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 2 || Bus error || access of nonexistent memory&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Address error || long/word access of odd address (&#039;&#039;68000&#039;&#039;)&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Illegal instruction || illegal opcode (other than Axxx or Fxxx)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Zero divide || processor division by zero&lt;br /&gt;
|-&lt;br /&gt;
| 6 || CHK instruction || register bounds error trap by CHK&lt;br /&gt;
|-&lt;br /&gt;
| 7 || TRAPV instruction || overflow error trap by TRAPV&lt;br /&gt;
|-&lt;br /&gt;
| 8 || Privilege violation || user execution of supervisor opcode&lt;br /&gt;
|-&lt;br /&gt;
| 9 || Trace || status register TRACE bit trap&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Line 1010 emulator || execution of opcode beginning with $A&lt;br /&gt;
|-&lt;br /&gt;
| 11 || Line 1111 emulator || execution of opcode beginning with $F&lt;br /&gt;
|-&lt;br /&gt;
| 32-47 || Trap instructions || TRAP &#039;&#039;N&#039;&#039; instruction where &#039;&#039;N&#039;&#039; = 0 to 15&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A system alert for a processor exception may set the high bit of the longword exception number to indicate an unrecoverable error (for example $8000¬†0005 for an unrecoverable processor exception #5). System alerts with more complex numbers are generally Amiga-specific software failures. These are built from the definitions in the &amp;amp;lt;exec/alerts.h&amp;amp;gt; include file.&lt;br /&gt;
&lt;br /&gt;
The actual stack frames generated for these traps are processor-dependent. The &#039;&#039;68010&#039;&#039;, &#039;&#039;68020&#039;&#039;, and &#039;&#039;68030&#039;&#039; processors will generate a different type of stack frame than the &#039;&#039;68000&#039;&#039;. If you plan on having your program handle its own traps, you should not make assumptions about the format of the supervisor stack frame. Check the flags in the AttnFlags field of the ExecBase structure for the type of processor in use and process the stack frame accordingly.&lt;br /&gt;
&lt;br /&gt;
=== Trap Handlers ===&lt;br /&gt;
&lt;br /&gt;
For compatibility with the &#039;&#039;68000&#039;&#039;, Exec performs trap handling in supervisor mode. This means that all task switching is disabled during trap handling.&lt;br /&gt;
&lt;br /&gt;
At entry to the task&#039;s trap handler, the system stack contains a processor-dependent trap frame as defined in the &#039;&#039;68000&#039;&#039;/&#039;&#039;10&#039;&#039;/&#039;&#039;20&#039;&#039;/&#039;&#039;30&#039;&#039; manuals. A longword exception number is added to this frame. That is, when a handler gains control, the top of stack contains the exception number and the trap frame immediately follows.&lt;br /&gt;
&lt;br /&gt;
To return from trap processing, remove the exception number from the stack (note that this is the supervisor stack, not the user stack) and then perform a return from exception (RTE).&lt;br /&gt;
&lt;br /&gt;
Because trap processing takes place in supervisor mode, with task dispatching disabled, it is strongly urged that you keep trap processing as short as possible or switch back to user mode from within your trap handler. If a trap handler already exists when you add your own trap handler, it is smart to propagate any traps that you do not handle down to the previous handler. This can be done by saving the previous address from tc_TrapCode and having your handler pass control to that address if the trap which occurred is not one you wish to handle.&lt;br /&gt;
&lt;br /&gt;
The following example installs a simple trap handler which intercepts processor divide-by-zero traps, and passes on all other traps to the previous default trap code. The example has two code modules which are linked together. The trap handler code is in assembler. The C module installs the handler, demonstrates its effectiveness, then restores the previous tc_TrapCode.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* trap_c.c - Execute me to compile me with SAS C 5.10&lt;br /&gt;
LC -b0 -cfistq -v -y -j73 trap_c.c&lt;br /&gt;
Blink FROM LIB:c.o,trap_c.o,trap_a.o TO trap LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit&lt;br /&gt;
&lt;br /&gt;
trap_c.c - C module of sample integer divide-by-zero trap&lt;br /&gt;
*/&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;exec/tasks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdlib.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdio.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE&lt;br /&gt;
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */&lt;br /&gt;
int chkabort(void) {return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
extern ULONG trapa();           /* assembler trap code in trap_a.asm */&lt;br /&gt;
&lt;br /&gt;
APTR oldTrapCode;&lt;br /&gt;
ULONG countdiv0;&lt;br /&gt;
&lt;br /&gt;
void main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
    struct Task *thistask;&lt;br /&gt;
    ULONG k,j;&lt;br /&gt;
&lt;br /&gt;
    thistask = FindTask(NULL);&lt;br /&gt;
&lt;br /&gt;
    /* Save our task&#039;s current trap code pointer */&lt;br /&gt;
    oldTrapCode = thistask-&amp;amp;gt;tc_TrapCode;&lt;br /&gt;
&lt;br /&gt;
    /* Point task to our assembler trap handler code.  Ours will just count */&lt;br /&gt;
    /* divide-by-zero traps, and pass other traps on to the normal TrapCode */&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = (APTR)trapa;&lt;br /&gt;
&lt;br /&gt;
    countdiv0 = 0L;&lt;br /&gt;
&lt;br /&gt;
    for(k=0; k&amp;amp;lt;4; k++)            /* Let&#039;s divide by zero a few times */&lt;br /&gt;
       {&lt;br /&gt;
       printf(&amp;amp;quot;dividing %ld by zero... &amp;amp;quot;,k);&lt;br /&gt;
       j = k/0L;&lt;br /&gt;
       printf(&amp;amp;quot;did it\n&amp;amp;quot;);&lt;br /&gt;
       }&lt;br /&gt;
    printf(&amp;amp;quot;\nDivide by zero happened %ld times\n&amp;amp;quot;,countdiv0);&lt;br /&gt;
&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = oldTrapCode;     /* Restore old trap code */&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;* trap_a.asm - Example trap handling code (leaves D0 intact).  Entered&lt;br /&gt;
* in supervisor mode with the following on the supervisor stack:&lt;br /&gt;
*    0(sp).l = trap#&lt;br /&gt;
*    4(sp) Processor dependent exception frame&lt;br /&gt;
&lt;br /&gt;
    INCLUDE &amp;amp;quot;exec/types.i&amp;amp;quot;&lt;br /&gt;
    INCLUDE &amp;amp;quot;libraries/dos.i&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
        XDEF _trapa&lt;br /&gt;
        XREF _countdiv0&lt;br /&gt;
        XREF _oldTrapCode&lt;br /&gt;
&lt;br /&gt;
        CODE&lt;br /&gt;
_trapa:                                 ; our trap handler entry&lt;br /&gt;
        CMPI.L  #5,(SP)                 ; is this a divide by zero ?&lt;br /&gt;
        BNE.S   notdiv0                 ; no&lt;br /&gt;
        ADD.L   #1,_countdiv0           ; yes, increment our div0 count&lt;br /&gt;
endtrap:&lt;br /&gt;
        ADDQ    #4,SP                   ; remove exception number from SSP&lt;br /&gt;
        RTE                             ; return from exception&lt;br /&gt;
notdiv0:&lt;br /&gt;
        TST.L   _oldTrapCode            ; is there another trap handler ?&lt;br /&gt;
        BEQ.S   endtrap                 ; no, so we&#039;ll exit&lt;br /&gt;
        MOVE.L  _oldTrapCode,-(SP)      ; yes, go on to old TrapCode&lt;br /&gt;
        RTS                             ; jumps to old TrapCode&lt;br /&gt;
&lt;br /&gt;
        END&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Trap Instructions ===&lt;br /&gt;
&lt;br /&gt;
The TRAP instructions in the &#039;&#039;68000&#039;&#039; generate traps 32-47. Because many independent pieces of system code may desire to use these traps, the AllocTrap() and FreeTrap() functions are provided. These work in a fashion similar to that used by AllocSignal() and FreeSignal(), mentioned above.&lt;br /&gt;
&lt;br /&gt;
Allocating a trap is simply a bookkeeping job within a task. It does not affect how the system calls the trap handler; it helps coordinate who owns what traps. Exec does nothing to determine whether or not a task is prepared to handle a particular trap. It simply calls your code. It is up to your program to handle the trap.&lt;br /&gt;
&lt;br /&gt;
To allocate any trap, you can use the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(-1)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;all trap instructions are in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can select a specific trap using this code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(3)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;trap #3 is in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To free a trap, you use the FreeTrap() function passing it the trap number to be freed.&lt;br /&gt;
&lt;br /&gt;
== Processor and Cache Control ==&lt;br /&gt;
&lt;br /&gt;
Exec provides a number of to control the processor mode and, if available, the caches. All these functions work independently of the specific &#039;&#039;M68000&#039;&#039; family processor type. This enables you to write code which correctly controls the state of both the &#039;&#039;MC68000&#039;&#039; and the &#039;&#039;MC68040&#039;&#039;. Along with processor mode and cache control, functions are provided to obtain information about the condition code register (CCR) and status register (SR). No functions are provided to control a paged memory management unit (PMMU) or floating point unit (FPU).&lt;br /&gt;
&lt;br /&gt;
Processor and Cache Control Functions&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| GetCC() || Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR() || Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState() || Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor() || Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState() || Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE() || Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU() || Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl() || Global cache control.&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA() || Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA() || Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Supervisor Mode ===&lt;br /&gt;
&lt;br /&gt;
While in supervisor mode, you have complete access to all data and registers, including those used for task scheduling and exceptions, and can execute privileged instructions. In application programs, normally only task trap code is directly executed in supervisor mode, to be compatible with the &#039;&#039;MC68000&#039;&#039;. For normal applications, it should never be necessary to switch to supervisor mode itself, only indirectly through Exec function calls. Remember that task switching is disabled while in supervisor mode. If it is absolutely needed to execute code in supervisor mode, keep it as brief as possible.&lt;br /&gt;
&lt;br /&gt;
Supervisor mode can only be entered when a &#039;&#039;680x0&#039;&#039; exception occurs (an interrupt or trap). The Supervisor() function allows you to trap an exception to a specified assembly function. In this function your have full access to all registers. No registers are saved when your function is invoked. You are responsible for restoring the system to a sane state when you are done. You must return to user mode with an RTE instruction. You must not return to user mode by executing a privileged instruction which clears the supervisor bit in the status register. Refer to a manual on the &#039;&#039;M68000&#039;&#039; family of CPUs for information about supervisor mode and available privileged instructions per processor type.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;MC68000&#039;&#039; has two stacks, the user stack (USP) and supervisor stack (SSP). As of the &#039;&#039;MC68020&#039;&#039; there are two supervisor stacks, the interrupt stack pointer (ISP) and the master stack pointer (MSP). The SuperState() function allows you to enter supervisor mode with the USP used as SSP. The function returns the SSP, which will be the MSP, if an &#039;&#039;MC68020&#039;&#039; or greater is used. Returning to user mode is done with the UserState() function. This function takes the SSP as argument, which must be saved when SuperState() is called. Because of possible problems with stack size, Supervisor() is to be preferred over SuperState().&lt;br /&gt;
&lt;br /&gt;
=== Status Register ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The processor status register bits can be set or read with the SetSR() function. This function operates in supervisor mode, thus both the upper and lower byte of the SR can be read or set. Be very sure you know what you are doing when you use this function to set bits in the SR and above all never try to use this function to enter supervisor mode. Refer to the &#039;&#039;M68000 Programmers Reference Manual&#039;&#039; by Motorola Inc. for information about the definition of individual SR bits per processor type.&lt;br /&gt;
&lt;br /&gt;
=== Condition Code Register ===&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;MC68000&#039;&#039; a copy of the processor condition codes can be obtained with the MOVE SR,&amp;amp;lt;ea&amp;amp;gt; instruction. On &#039;&#039;MC68010&#039;&#039; processors and up however, the instruction MOVE CCR,&amp;amp;lt;ea&amp;amp;gt; must be used. Using the specific &#039;&#039;MC68000&#039;&#039; instruction on later processors will cause a &#039;&#039;680x0&#039;&#039; exception since it is a privileged instruction on those processors. The GetCC() function provides a processor independent way of obtaining a copy of the condition codes. For all processors there are 5 bits which can indicate the result of an integer or a system control instruction:&lt;br /&gt;
&lt;br /&gt;
* X - extend&lt;br /&gt;
* N - negative&lt;br /&gt;
* Z - zero&lt;br /&gt;
* V - overflow&lt;br /&gt;
* C - carry&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;X&#039;&#039;&#039; bit is used for multiprecision calculations. If used, it is copy of the carry bit. The other bits state the result of a processor operation.&lt;br /&gt;
&lt;br /&gt;
=== Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
As of the &#039;&#039;MC68020&#039;&#039; all processors have an instruction cache, 256 bytes on the &#039;&#039;MC68020&#039;&#039; and &#039;&#039;MC68030&#039;&#039; and 4 KBytes on a &#039;&#039;MC68040&#039;&#039;. The &#039;&#039;MC68030&#039;&#039; and &#039;&#039;MC68040&#039;&#039; have data caches as well, 256 bytes and 4 KBytes respectively. All the processors load instructions ahead of the program counter (PC), albeit it that the &#039;&#039;MC68000&#039;&#039; and &#039;&#039;MC68010&#039;&#039; only prefetch one and two words respectively. This means the CPU loads instructions ahead of the current program counter. For this reason self-modifying code is strongly discouraged. If your code modifies or decrypts itself just ahead of the program counter, the pre-fetched instructions may not match the modified instructions. If self-modifying code must be used, flushing the cache is the safest way to prevent this.&lt;br /&gt;
&lt;br /&gt;
=== DMA Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
The CachePreDMA() and CachePostDMA() functions allow you to flush the data cache before and after Direct Memory Access. Typically only DMA device drivers benefit from this. These functions take the processor type, possible MMU and cache mode into account. When no cache is available they end up doing nothing. These functions can be replaced with ones suitable for different cache hardware. Refer to the SDK for implementation specifics.&lt;br /&gt;
&lt;br /&gt;
Since DMA device drivers read and write directly to memory, they are effected by the CopyBack feature of the &#039;&#039;MC68040&#039;&#039; (explained below). Using DMA with CopyBack mode requires a cache flush. If a DMA device needs to read RAM via DMA, it must make sure that the data in the caches has been written to memory first, by calling CachePreDMA(). In case of a write to memory, the DMA device should first clear the caches with CachePreDMA(), write the data and flush the caches again with CachePostDMA().&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following chart gives a brief description of the Exec functions that control tasks. See the SDK for details about each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Exec Task Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AddTask()&lt;br /&gt;
| Add a task to the system.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTrap()&lt;br /&gt;
| Allocate a processor trap vector.&lt;br /&gt;
|-&lt;br /&gt;
| Disable()&lt;br /&gt;
| Disable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| Enable()&lt;br /&gt;
| Enable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| FindTask()&lt;br /&gt;
| Find a specific task.&lt;br /&gt;
|-&lt;br /&gt;
| Forbid()&lt;br /&gt;
| Forbid task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| FreeTrap()&lt;br /&gt;
| Release a process trap.&lt;br /&gt;
|-&lt;br /&gt;
| Permit()&lt;br /&gt;
| Permit task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| SetTaskPri()&lt;br /&gt;
| Set the priority of a task.&lt;br /&gt;
|-&lt;br /&gt;
| RemTask()&lt;br /&gt;
| Remove a task from the system.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE()&lt;br /&gt;
| Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU()&lt;br /&gt;
| Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl()&lt;br /&gt;
| Global cache control (V37).&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA()&lt;br /&gt;
| Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA()&lt;br /&gt;
| Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| GetCC()&lt;br /&gt;
| Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR()&lt;br /&gt;
| Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState()&lt;br /&gt;
| Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor()&lt;br /&gt;
| Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState()&lt;br /&gt;
| Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CreateTask()&lt;br /&gt;
| Setup and add a new task.&lt;br /&gt;
|-&lt;br /&gt;
| DeleteTask()&lt;br /&gt;
| Delete a task created with CreateTask().&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Mutexes&amp;diff=3415</id>
		<title>Exec Mutexes</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Mutexes&amp;diff=3415"/>
		<updated>2012-07-10T06:04:17Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Creating a Mutex */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Exec Mutexes ==&lt;br /&gt;
&lt;br /&gt;
Mutexes are similar to [[Exec_Semaphores|Exec Semaphores]] although the protocol defined on them is much simpler.&lt;br /&gt;
&lt;br /&gt;
Mutexes can not be obtained shared or in an asynchronous fashion. If these features are required then a semaphore will likely be necessary. However, if the mutex isn&#039;t locked when an attempt is made to obtain it, it will acquire the lock without the use of Forbid/Permit locking. This puts less overhead on the system as a whole so a mutex should be preferred over a semaphore in most cases.&lt;br /&gt;
&lt;br /&gt;
A mutex is also opaque. This means the programmer is not allowed to see the internals of a mutex. The reason for this design choice is to make it possible for the system to be able to break deadlocks when necessary and free mutexes automatically. SignalSemaphores are not opaque and may also be allocated statically which makes it impossible for the system to safely break a deadlock or free a semaphore automatically.&lt;br /&gt;
&lt;br /&gt;
A mutex may be recursive or not. A recursive mutex is one which allows the same task which obtained it to obtain it again. In most cases, a recursive mutex is what is required so the default is to create a recursive mutex.&lt;br /&gt;
&lt;br /&gt;
For mutexes to work correctly, there are two restrictions that &#039;&#039;must&#039;&#039; be observed at all times:&lt;br /&gt;
&lt;br /&gt;
# All tasks using shared data that is protected by a mutex must &#039;&#039;always ask for the mutex first before accessing the data&#039;&#039;. If some task accesses the data directly without first going through the mutex, the data may be corrupted. No task will have safe access to the data.&lt;br /&gt;
# A deadlock will occur if a task that owns a mutex on some data inadvertently calls another task which tries to get the mutex on that same data. Deadlocks and other such issues are beyond the scope of this manual. For more details on deadlocks and other problems of shared data in a multitasking system and the methods used to prevent them, refer to [http://en.wikipedia.org/wiki/Deadlock Wikipedia].&lt;br /&gt;
&lt;br /&gt;
=== Creating a Mutex ===&lt;br /&gt;
&lt;br /&gt;
There is only one way to create a Mutex and that is via the AllocSysObject() function with an object type of ASOT_MUTEX. The mutex is an opaque pointer which is not open to the public.&lt;br /&gt;
&lt;br /&gt;
Here is an example showing how to create a mutex:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR mutex = IExec-&amp;gt;AllocSysObjectTags(ASOT_MUTEX,&lt;br /&gt;
  ASOMUTEX_Recursive, FALSE,  // The default is to create a recursive mutex.&lt;br /&gt;
TAG_END);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Obtaining a Mutex ===&lt;br /&gt;
&lt;br /&gt;
The MutexObtain() function can be used to get a lock on a mutex. If another task currently has a lock on the mutex, your task will be put to sleep until all locks on the the mutex are released. If the same task tries to lock the mutex a second time the behaviour depends on whether the mutex was created with the recursive property or not.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Mutex Nesting.&#039;&#039; Mutexes have optional nesting. If the mutex is recursive, a lock by the same task on the same mutex will always succeed. If the mutex is not recursive, a lock by the same task on the same mutex will always fail. Which mode is most appropriate depends on the design of the application.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
To obtain a mutex use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR mutex;&lt;br /&gt;
IExec-&amp;gt;MutexObtain(mutex);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Checking a Mutex ===&lt;br /&gt;
&lt;br /&gt;
When you attempt to obtain a mutex with MutexObtain(), your task will be put to sleep if the mutex is not currently available. If you do not want to wait, you can call MutexAttempt(). If the mutex is available for locking, MutexAttempt() obtains it for you and returns TRUE. If it is not available, the function returns FALSE immediately instead of waiting for the mutex to be released.&lt;br /&gt;
&lt;br /&gt;
The MutexAttemptWithSignal() function is similar to MutexAttempt() but will block if the mutex cannot be obtained. What makes MutexAttemptWithSignal() special is that it will unblock if the mutex is released or if a predefined signal is received. Being able to block on a mutex or a signal removes the need for any Forbid()/Permit() locking in such situations. If the mutex is available for locking, MutexAttempWithSignal() obtains it for you and returns a zero value. If it is not available and a signal is received, it will return the signal mask indicating which signal(s) were received.&lt;br /&gt;
&lt;br /&gt;
To attempt to obtain a mutex, use the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR mutex;&lt;br /&gt;
IExec-&amp;gt;MutexAttempt(mutex);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To make an attempt to obtain a mutex or a signal, the following code should be used:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR mutex;&lt;br /&gt;
uint32 sigmask;&lt;br /&gt;
&lt;br /&gt;
IExec-&amp;gt;MutexAttemptWithSignal(mutex, sigmask);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Releasing a Mutex ===&lt;br /&gt;
&lt;br /&gt;
Once you have obtained the mutex and completed any operations on the mutex protected object, you should release the mutex. The MutexRelease() function does this. For each successful MutexObtain(), MutexAttempt() and MutexAttemptWithSignal() call you make, you must have a matching MutexRelease() call. &lt;br /&gt;
&lt;br /&gt;
=== Removing a Mutex ===&lt;br /&gt;
&lt;br /&gt;
Mutex resources can only be freed if the mutex is not locked. The FreeSysObject() function will not block your task if locks are still outstanding and it will result in undefined system behaviour.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IExec-&amp;gt;FreeSysObject(ASOT_MUTEX, mutex);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Mutex Example ==&lt;br /&gt;
&lt;br /&gt;
A simple &amp;quot;do nothing&amp;quot; example of Exec mutex use is shown below. When the mutex is owned by a task, attempted access by other tasks will block. A nesting count is maintained, so the current task can safely call ObtainMutex() on the same mutex.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// mutex.c - Exec mutex example - compile with gcc -o mutex mutex.c&lt;br /&gt;
#include &amp;lt;exec/exectags.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
APTR LockMutex;&lt;br /&gt;
&lt;br /&gt;
int main(int argc,char *argv[])&lt;br /&gt;
{&lt;br /&gt;
    LockMutex = IExec-&amp;gt;AllocSysObjectTags(ASOT_MUTEX,&lt;br /&gt;
      ASOMUTEX_Recursive, TRUE,  // If set to FALSE, no nesting will be allowed.&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (LockMutex != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        IExec-&amp;gt;MutexObtain(LockMutex);  // Task now owns the mutex.&lt;br /&gt;
        // ...&lt;br /&gt;
        IExec-&amp;gt;MutexRelease(LockMutex); // Task has released the mutex.&lt;br /&gt;
&lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_MUTEX, LockMutex);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following charts give a brief description of the Exec semaphore functions. See the SDK for details about each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Exec Mutex Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AllocSysObject(ASOT_MUTEX)&lt;br /&gt;
| Allocate and initialize a new semaphore.&lt;br /&gt;
|-&lt;br /&gt;
| MutexAttempt()&lt;br /&gt;
| Try to get an exclusive lock on a mutex without blocking.&lt;br /&gt;
|-&lt;br /&gt;
| MutexAttemptWithSignal()&lt;br /&gt;
| Try to get an exclusive lock on a mutex or a signal mask.&lt;br /&gt;
|-&lt;br /&gt;
| FreeSysObject(ASOT_MUTEX)&lt;br /&gt;
| Free a mutex. &lt;br /&gt;
|-&lt;br /&gt;
| MutexObtain()&lt;br /&gt;
| Try to get exclusive access to a mutex.&lt;br /&gt;
|-&lt;br /&gt;
| MutexRelease()&lt;br /&gt;
| Release the lock on a mutex.&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3414</id>
		<title>Exec Tasks</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Exec_Tasks&amp;diff=3414"/>
		<updated>2012-07-10T04:33:34Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Task Stack */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{NeedUpdate}}&lt;br /&gt;
== Exec Tasks ==&lt;br /&gt;
&lt;br /&gt;
One of the most powerful features of the Amiga operating system is its ability to run and manage multiple independent program tasks, providing each task with processor time based on their priority and activity. These tasks include system device drivers, background utilities, and user interface environments, as well as normal application programs. This multitasking capability is provided by the Exec library&#039;s management of task creation, termination, scheduling, event signals, traps, exceptions, and mutual exclusion.&lt;br /&gt;
&lt;br /&gt;
This section deals with Exec on a lower level than most applications programmers need and assumes you are already familiar with the Exec basics discussed in the [[Introduction_to_Exec|Introduction to Exec]].&lt;br /&gt;
&lt;br /&gt;
== Task Structure ==&lt;br /&gt;
&lt;br /&gt;
Exec maintains task context and state information in a task-control data structure. Like most Exec structures, Task structures are dynamically linked onto various task queues through the use of an embedded Exec list Node structure (see the Lists chapter). Any task can find its own task structure by calling FindTask(NULL). The C-language form of this structure is defined in the &amp;amp;lt;exec/tasks.h&amp;amp;gt; include file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Task  {&lt;br /&gt;
    struct Node tc_Node;&lt;br /&gt;
    UBYTE       tc_Flags;&lt;br /&gt;
    UBYTE       tc_State;&lt;br /&gt;
    BYTE        tc_IDNestCnt;   /* intr disabled nesting */&lt;br /&gt;
    BYTE        tc_TDNestCnt;   /* task disabled nesting */&lt;br /&gt;
    ULONG       tc_SigAlloc;    /* sigs allocated */&lt;br /&gt;
    ULONG       tc_SigWait;     /* sigs we are waiting for */&lt;br /&gt;
    ULONG       tc_SigRecvd;    /* sigs we have received */&lt;br /&gt;
    ULONG       tc_SigExcept;   /* sigs we will take excepts for */&lt;br /&gt;
    UWORD       tc_TrapAlloc;   /* traps allocated */&lt;br /&gt;
    UWORD       tc_TrapAble;    /* traps enabled */&lt;br /&gt;
    APTR        tc_ExceptData;  /* points to except data */&lt;br /&gt;
    APTR        tc_ExceptCode;  /* points to except code */&lt;br /&gt;
    APTR        tc_TrapData;    /* points to trap code */&lt;br /&gt;
    APTR        tc_TrapCode;    /* points to trap data */&lt;br /&gt;
    APTR        tc_SPReg;       /* stack pointer */&lt;br /&gt;
    APTR        tc_SPLower;     /* stack lower bound */&lt;br /&gt;
    APTR        tc_SPUpper;     /* stack upper bound + 2*/&lt;br /&gt;
    VOID      (*tc_Switch)();   /* task losing CPU */&lt;br /&gt;
    VOID      (*tc_Launch)();   /* task getting CPU */&lt;br /&gt;
    struct List tc_MemEntry;    /* allocated memory */&lt;br /&gt;
    APTR        tc_UserData;    /* per task data */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Most of these fields are not relevant for simple tasks; they are used by Exec for state and administrative purposes. A few fields, however, are provided for the advanced programs that support higher level environments (as in the case of &#039;&#039;processes&#039;&#039;) or require precise control (as in &#039;&#039;devices&#039;&#039;). The following sections explain these fields in more detail.&lt;br /&gt;
&lt;br /&gt;
== Task Creation ==&lt;br /&gt;
&lt;br /&gt;
To create a new task manually you must allocate a task structure, initialize its various fields, and then link it into Exec with a call to AddTask(). This is not the recommended way to create a task. This detailed information is only being provided for reference.&lt;br /&gt;
&lt;br /&gt;
The task structure may be allocated by calling the AllocVecTags() function with the MEMF_SHARED attribute and AVT_ClearWithValue tag.&lt;br /&gt;
&lt;br /&gt;
The Task fields that require initialization depend on how you intend to use the task. For the simplest of tasks, only a few fields must be initialized:&lt;br /&gt;
&lt;br /&gt;
; tc_Node&lt;br /&gt;
: The task list node structure. This includes the task&#039;s priority, its type, and its name (refer to [[Exec_Lists_and_Queues|Exec Lists and Queues]] chapter).&lt;br /&gt;
&lt;br /&gt;
; tc_SPLower&lt;br /&gt;
: The lower memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPUpper&lt;br /&gt;
: The upper memory bound of the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
; tc_SPReg&lt;br /&gt;
: The initial stack pointer. Because task stacks grow &#039;&#039;downward&#039;&#039; in memory, this field is usually set to the same value as tc_SPUpper.&lt;br /&gt;
&lt;br /&gt;
Zeroing all other unused fields will cause Exec to supply the appropriate system default values. Allocating the structure with the AVT_ClearWithValue tag is an easy way to be sure that this happens.&lt;br /&gt;
&lt;br /&gt;
Once the structure has been initialized, it must be linked to Exec. This is done with a call to AddTask() in which the following parameters are specified:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
AddTask(struct Task *task, APTR initialPC, APTR finalPC )&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The task argument is a pointer to your initialized Task structure. Set initialPC to the entry point of your task code. This is the address of the first instruction the new task will execute.&lt;br /&gt;
&lt;br /&gt;
Set finalPC to the address of the finalization code for your task. This is a code section that will receive control if the initialPC routine ever performs a return. This exists to prevent your task from being launched into random memory upon an accidental return. The finalPC routine should usually perform various program-related clean-up duties and should then remove the task. If a zero is supplied for this parameter, Exec will use its default finalization code (which simply calls the RemTask() function).&lt;br /&gt;
&lt;br /&gt;
=== Task Creation With CreateTask() ===&lt;br /&gt;
&lt;br /&gt;
The recommended of creating a task is with CreateTask() or CreateTaskTags() functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
CreateTaskTags(CONST_STRPTR name, int32 priority, APTR initialPC, uint32 stacksize, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A task created with CreateTaskTags() may be removed with the DeleteTask() function, or it may simply return when it is finished. CreateTaskTags() adds a MemList to the tc_MemEntry of the task it creates, describing all memory it has allocated for the task, including the task stack and the Task structure itself. This memory will be deallocated by Exec when the task is either explicitly removed (RemTask() or DeleteTask()) or when it exits to Exec&#039;s default task removal code (RemTask()).&lt;br /&gt;
&lt;br /&gt;
Depending on the priority of a new task and the priorities of other tasks in the system, the newly added task may begin execution immediately.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Sharing Library Pointers&#039;&#039; Although in most cases it is possible for a parent task to pass a library base to a child task so the child can use that library, for some libraries, this is not possible. For this reason, the only library base sharable between tasks is Exec&#039;s library base.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Here is an example of simple task creation. In this example there is no coordination or communication between the main process and the simple task it has created. A more complex example might use named ports and messages to coordinate the activities and shutdown of two tasks. Because our task is very simple and never calls any system functions which could cause it to be signaled or awakened, we can safely remove the task at any time.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Keep This In Mind.&#039;&#039; Because the simple task&#039;s code is a function in our program, we must stop the subtask before exiting.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// simpletask.c - Uses the function CreateTaskTags() to create a simple subtask.&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/tasks.h&amp;gt;&lt;br /&gt;
#include &amp;lt;libraries/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define STACK_SIZE 16000&lt;br /&gt;
&lt;br /&gt;
uint32 sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
/* our function prototypes */&lt;br /&gt;
void simpletask(void);&lt;br /&gt;
void cleanexit(CONST_STRPTR, int32);&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    sharedvar = 0;&lt;br /&gt;
&lt;br /&gt;
    struct Task *task = CreateTaskTags(&amp;quot;SimpleTask&amp;quot;, 0 , simpletask, STACK_SIZE,&lt;br /&gt;
      TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if(task == NULL)  cleanexit(&amp;quot;Can&#039;t create task&amp;quot;, RETURN_FAIL);&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;This program initialized a variable to zero, then started a\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;separate task which is incrementing that variable right now,\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;while this program waits for you to press RETURN.\n&amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;Press RETURN now: &amp;quot;);&lt;br /&gt;
    IDOS-&amp;gt;FGetC( IDOS-&amp;gt;Input() );&lt;br /&gt;
&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;The shared variable now equals %ld\n&amp;quot;, sharedvar);&lt;br /&gt;
&lt;br /&gt;
    /* We can simply remove the task we added because our simpletask does not make */&lt;br /&gt;
    /* any system calls which could cause it to be awakened or signalled later.    */&lt;br /&gt;
    IExec-&amp;gt;Forbid();&lt;br /&gt;
    IEXec-&amp;gt;DeleteTask(task);&lt;br /&gt;
    IExec-&amp;gt;Permit();&lt;br /&gt;
    cleanexit(&amp;quot;&amp;quot;, RETURN_OK);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void simpletask(void)&lt;br /&gt;
{&lt;br /&gt;
    while(sharedvar &amp;lt; 0x8000000) sharedvar++;&lt;br /&gt;
    /* Wait forever because main() is going to RemTask() us */&lt;br /&gt;
    IExec-&amp;gt;Wait(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void cleanexit(CONST_STRPTR s, int32 e)&lt;br /&gt;
{&lt;br /&gt;
    if(s != NULL) IDOS-&amp;gt;Printf(&amp;quot;%s\n&amp;quot;, s);&lt;br /&gt;
    exit(e);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Stack ===&lt;br /&gt;
&lt;br /&gt;
Every task requires a stack. All normal code execution occurs on this task stack. Special modes of execution (processor traps and system interrupts for example) execute on a single &#039;&#039;supervisor mode&#039;&#039; stack and do not directly affect task stacks.&lt;br /&gt;
&lt;br /&gt;
Task stacks are normally used to store local variables, subroutine return addresses, and saved register values. Additionally, when a task loses the processor, all of its current registers are preserved on this stack (with the exception of the stack pointer itself, which must be saved in the task structure).&lt;br /&gt;
&lt;br /&gt;
The amount of stack used by a task can vary widely. As a general rule of thumb, a minimum stack size of 16000 bytes should be used. If any GUI elements are involved, a minimum stack size of 80000 bytes is recommended.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Because stack-bounds checking is not provided as a service of Exec, it is important to provide enough space for your task stack. Stack overflows are always difficult to debug and may result not only in the erratic failure of your task but also in the mysterious malfunction of other Amiga subsystems.&#039;&#039;&#039; Some compilers may provide a stack-checking option.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;You Can&#039;t Always Check The Stack.&#039;&#039; Such stack-checking options generally cannot be used if part of your code will be running on the system stack (interrupts, exceptions, handlers, servers) or on a different task&#039;s stack (libraries, devices, created tasks).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When choosing your stack size, do not cut it too close. Remember that any recursive routines in your code may use varying amounts of stack, and that future versions of system routines may use additional stack variables. By dynamically allocating buffers and arrays, most application programs can be designed to function comfortably within the recommended minimum process stack size of 16000 bytes.&lt;br /&gt;
&lt;br /&gt;
=== Task Priority ===&lt;br /&gt;
&lt;br /&gt;
A task&#039;s priority indicates its importance relative to other tasks. Higher-priority tasks receive the processor before lower-priority tasks do. Task priority is stored as a signed number ranging from -128 to +127. Higher priorities are represented by more positive values; zero is considered the neutral priority. Normally, system tasks execute somewhere in the range of +20 to -20, and most application tasks execute at priority 0.&lt;br /&gt;
&lt;br /&gt;
It is not wise to needlessly raise a task&#039;s priority. Sometimes it may be necessary to carefully select a priority so that the task can properly interact with various system tasks. The SetTaskPri() Exec function is provided for this purpose.&lt;br /&gt;
&lt;br /&gt;
== Task Termination ==&lt;br /&gt;
&lt;br /&gt;
Task termination may occur as the result of a number of situations:&lt;br /&gt;
&lt;br /&gt;
* A program returning from its initialPC routine and dropping into its finalPC routine or the system default finalizer.&lt;br /&gt;
* A task trap that is too serious for a recovery action. This includes traps like processor bus error, odd address access errors, etc.&lt;br /&gt;
* A trap that is not handled by the task. For example, the task might be terminated if your code happened to encounter a processor TRAP instruction and you did not provide a trap handling routine.&lt;br /&gt;
* An explicit call to RemTask() or DeleteTask().&lt;br /&gt;
&lt;br /&gt;
Task termination involves the deallocation of system resources and the removal of the task structure from Exec. The most important part of task termination is the deallocation of system resources. A task must return all memory that it allocated for its private use, it must terminate any outstanding I/O commands, and it must close access to any system libraries or devices that it has opened.&lt;br /&gt;
&lt;br /&gt;
It is wise to adopt a strategy for task clean-up responsibility. You should decide whether resource allocation and deallocation is the duty of the creator task or the newly created task. Often it is easier and safer for the creator to handle the resource allocation and deallocation on behalf of its offspring. In such cases, before removing the child task, you must make sure it is in a safe state such as Wait(0L) and not still using a resources or waiting for an event or signal that might still occur.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitabl&amp;quot;&lt;br /&gt;
| &#039;&#039;NOTE:&#039;&#039; Certain resources, such as signals and created ports, must be allocated and deallocated by the same task that will wait on them. Also note that if your subtask code is part of your loaded program, you must not allow your program to exit before its subtasks have cleaned up their allocations, and have been either deleted or placed in a safe state such as Wait(0).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Exclusion ==&lt;br /&gt;
&lt;br /&gt;
From time to time the advanced system program may find it necessary to access global system data structures. Because these structures are shared by the system and by other tasks that execute asynchronously to your task, a task must prevent other tasks from using these structures while it is reading from or writing to them. This can be accomplished by preventing the operating system from switching tasks by &#039;&#039;forbidding&#039;&#039; or &#039;&#039;disabling&#039;&#039;. A section of code that requires the use of either of these mechanisms to lock out access by others is termed a &#039;&#039;critical section&#039;&#039;. Use of these methods is discouraged. For arbitrating access to data between your tasks, &#039;&#039;mutexes&#039;&#039; are a superior solution. (See [[Exec_Mutexes|Exec Mutexes]])&lt;br /&gt;
&lt;br /&gt;
=== Forbidding Task Switching ===&lt;br /&gt;
&lt;br /&gt;
Forbidding is used when a task is accessing shared structures that might also be accessed at the same time from another task. It effectively eliminates the possibility of simultaneous access by imposing &#039;&#039;non-preemptive&#039;&#039; task scheduling. This has the net effect of disabling multitasking for as long as your task remains in its running state.&lt;br /&gt;
&lt;br /&gt;
While forbidden, your task will continue running until it performs a call to Wait() or exits from the forbidden state. Interrupts will occur normally, but no new tasks will be dispatched, &#039;&#039;regardless of their priorities&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a task running in the forbidden state calls the Wait() function, directly or indirectly, it implies a temporary exit from its forbidden state. Since almost all stdio, device I/O, and file I/O functions must Wait() for I/O completion, performing such calls will cause your task to Wait(), temporarily breaking the forbid. While the task is waiting, the system will perform normally. When the task receives one of the signals it is waiting for, it will again reenter the forbidden state.&lt;br /&gt;
&lt;br /&gt;
To become forbidden, a task calls the Forbid() function. To escape, the Permit() function is used. The use of these functions may be nested with the expected affects; you will not exit the forbidden mode until you call the outermost Permit().&lt;br /&gt;
&lt;br /&gt;
As an example, the Exec task list should only be accessed when in a Forbid() state. Accessing the list without forbidding could lead to incorrect results or it could crash the entire system. To access the task list also requires the program to disable interrupts which is discussed in the next section.&lt;br /&gt;
&lt;br /&gt;
=== Disabling Tasks ===&lt;br /&gt;
&lt;br /&gt;
Disabling is similar to forbidding, but it also prevents interrupts from occurring during a critical section. Disabling is required when a task accesses structures that are shared by interrupt code. It eliminates the possibility of an interrupt accessing shared structures by preventing interrupts from occurring. Use of disabling is strongly discouraged.&lt;br /&gt;
&lt;br /&gt;
To disable interrupts you can call the Disable() function. To enable interrupts again, use the Enable() function.&lt;br /&gt;
&lt;br /&gt;
Like forbidden sections, disabled sections can be nested. To restore normal interrupt processing, an Enable() call must be made for every Disable(). Also like forbidden sections, any direct or indirect call to the Wait() function will enable interrupts until the task regains the processor.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;WARNING: It is important to realize that there is a danger in using disabled sections&#039;&#039;. Because the software on the Amiga depends heavily on its interrupts occurring in nearly real time, you cannot disable for more than a very brief instant. Disabling interrupts for more than 250 microseconds can interfere with the normal operation of vital system functions, especially serial I/O.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It is never necessary to both Disable() and Forbid(). Because disabling prevents interrupts, it also prevents preemptive task scheduling. When disable is used within an interrupt, it will have the effect of locking out all higher level interrupts (lower level interrupts are automatically disabled by the CPU). Many Exec lists can only be accessed while disabled. Suppose you want to print the names of all system tasks. You would need to access both the TaskReady and TaskWait lists from within a single disabled section. In addition, you must avoid calling system functions that would break a disable by an indirect call to Wait() (Printf() for example). In this example, the names are gathered into a list while task switching is disabled. Then task switching is enabled and the names are printed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
// tasklist.c - Snapshots and prints the ExecBase task list&lt;br /&gt;
#include &amp;lt;exec/types.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/lists.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/nodes.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/memory.h&amp;gt;&lt;br /&gt;
#include &amp;lt;exec/execbase.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;proto/exec.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/dos.h&amp;gt;&lt;br /&gt;
#include &amp;lt;proto/utility.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CONST_STRPTR VersTag = &amp;quot;$VER: tasklist 53.1 (21.6.2012)&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
extern struct ExecBase *SysBase;&lt;br /&gt;
&lt;br /&gt;
/* Use extended structure to hold task information */&lt;br /&gt;
struct TaskNode {&lt;br /&gt;
    struct Node tn_Node;&lt;br /&gt;
    uint32 tn_TaskAddress;&lt;br /&gt;
    uint32 tn_SigAlloc;&lt;br /&gt;
    uint32 tn_SigWait;&lt;br /&gt;
    TEXT tn_Name[32];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    struct TaskNode *tnode = NULL;&lt;br /&gt;
    struct TaskNode *rnode = NULL;&lt;br /&gt;
&lt;br /&gt;
    /* Allocate memory for our list */&lt;br /&gt;
    struct List *ourtasklist = IExec-&amp;gt;AllocSysObjectTags(ASOT_LIST, TAG_END);&lt;br /&gt;
&lt;br /&gt;
    if (ourtasklist != NULL)&lt;br /&gt;
    {&lt;br /&gt;
        /* Make sure tasks won&#039;t switch lists or go away */&lt;br /&gt;
        IExec-&amp;gt;Disable();&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task WAIT list */&lt;br /&gt;
        struct List *exectasklist = &amp;amp;(SysBase-&amp;gt;TaskWait);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
&lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
            }&lt;br /&gt;
            else break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Snapshot task READY list */&lt;br /&gt;
        exectasklist = &amp;amp;(SysBase-&amp;gt;TaskReady);&lt;br /&gt;
        for (struct Node *execnode = IExec-&amp;gt;GetHead(exectasklist);&lt;br /&gt;
             execnode != NULL; execnode = IExec-&amp;gt;GetSucc(execnode))&lt;br /&gt;
        {&lt;br /&gt;
            tnode = IExec-&amp;gt;AllocVecTags(sizeof(struct TaskNode),&lt;br /&gt;
              AVT_ClearWithValue, 0,&lt;br /&gt;
              TAG_END);&lt;br /&gt;
        &lt;br /&gt;
            if (tnode != NULL)&lt;br /&gt;
            {&lt;br /&gt;
                /* Save task information we want to print */&lt;br /&gt;
                IUtility-&amp;gt;Strlcpy(tnode-&amp;gt;tn_Name, execnode-&amp;gt;ln_Name, sizoeof(tnode-&amp;gt;tn_Name));&lt;br /&gt;
                tnode-&amp;gt;tn_Node.ln_Pri = execnode-&amp;gt;ln_Pri;&lt;br /&gt;
                tnode-&amp;gt;tn_TaskAddress = (uint32)execnode;&lt;br /&gt;
                tnode-&amp;gt;tn_SigAlloc    = ((struct Task *)execnode)-&amp;gt;tc_SigAlloc;&lt;br /&gt;
                tnode-&amp;gt;tn_SigWait     = ((struct Task*)execnode)-&amp;gt;tc_SigWait;&lt;br /&gt;
                IExec-&amp;gt;AddTail(ourtasklist, (struct Node *)tnode);&lt;br /&gt;
                if(!rnode)  rnode = tnode;  /* first READY task */&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Re-enable interrupts and taskswitching */&lt;br /&gt;
        IExec-&amp;gt;Enable();&lt;br /&gt;
&lt;br /&gt;
        /* Print now (printing above would have defeated a Forbid or Disable) */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;Pri Address     SigAlloc    SigWait    Taskname\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        struct TaskNode *node = (struct TaskNode *)IExec-&amp;gt;GetHead(ourtasklist);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nWAITING:\n&amp;quot;);&lt;br /&gt;
        while (tnode = (struct TaskNode *)IExec-&amp;gt;GetSucc((struct Node*)node))&lt;br /&gt;
        {&lt;br /&gt;
            if(tnode == rnode)&lt;br /&gt;
                IDOS-&amp;gt;Printf(&amp;quot;\nREADY:\n&amp;quot;);  /* we set rnode above */&lt;br /&gt;
                &lt;br /&gt;
            IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                    node-&amp;gt;tn_Node.ln_Pri, node-&amp;gt;tn_TaskAddress, node-&amp;gt;tn_SigAlloc,&lt;br /&gt;
                    node-&amp;gt;tn_SigWait, node-&amp;gt;tn_Name);&lt;br /&gt;
&lt;br /&gt;
            /* Free the memory, no need to remove the node, referenced once only */&lt;br /&gt;
            IExec-&amp;gt;FreeVec(node);&lt;br /&gt;
            node = tnode;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        IExec-&amp;gt;FreeSysObject(ASOT_LIST, ourtasklist);&lt;br /&gt;
&lt;br /&gt;
        /* Say who we are */&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;\nTHIS TASK:\n&amp;quot;);&lt;br /&gt;
        struct Task *task = FindTask(NULL);&lt;br /&gt;
        IDOS-&amp;gt;Printf(&amp;quot;%02d  0x%08lx  0x%08lx  0x%08lx %s\n&amp;quot;,&lt;br /&gt;
                task-&amp;gt;tc_Node.ln_Pri, task, task-&amp;gt;tc_SigAlloc,&lt;br /&gt;
                task-&amp;gt;tc_SigWait, task-&amp;gt;tc_Node.ln_Name);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Task Mutexes and Semaphores ===&lt;br /&gt;
&lt;br /&gt;
Mutexes and semaphores can be used for the purposes of mutual exclusion. With this method of locking, all tasks agree on a locking convention before accessing shared data structures. Tasks that do not require access are not affected and will run normally, so this type of exclusion is considered preferable to forbidding and disabling. This form of exclusion is explained in more detail in [[Exec_Mutexes|Mutexes]] and [[Exec_Semaphores|Semaphores]].&lt;br /&gt;
&lt;br /&gt;
== Task Exceptions ==&lt;br /&gt;
&lt;br /&gt;
Exec can provide a task with its own task-local &amp;quot;interrupt&amp;quot; called an &#039;&#039;exception&#039;&#039;. When some exceptional event occurs, an Exec exception occurs which stops a particular task from executing its normal code and forces it to execute a special, task-specific exception handling routine.&lt;br /&gt;
&lt;br /&gt;
To set up an exception routine for a task requires setting values in the task&#039;s control structure (the Task structure). The tc_ExceptCode field should point to the task&#039;s exception handling routine. If this field is zero, Exec will ignore all exceptions. The tc_ExceptData field should point to any data the exception routine needs.&lt;br /&gt;
&lt;br /&gt;
Exec exceptions work using signals. When a specific signal or signals occur, Exec will stop a task and execute its exception routine. Use the Exec function SetExcept() to tell Exec which of the task&#039;s signals should trigger the exception.&lt;br /&gt;
&lt;br /&gt;
When an exception occurs, Exec stops executing the tasks normal code and jumps immediately into the exception routine, no matter what the task was doing. The exception routine operates in the same context the task&#039;s normal code; it operates in the CPU&#039;s user mode and uses the task&#039;s stack.&lt;br /&gt;
&lt;br /&gt;
Before entering the exception routine, Exec pushes the normal task code&#039;s context onto the stack. Exec then puts certain parameters in the processor registers for the exception routine to use. D0 contains a signal mask indicating which signal bit or bits caused the exception. Exec disables these signals when the task enters its exception routine. If more than one signal bit is set (i.e. if two signals occurred simultaneously), it is up to the exception routine to decide in what order to process the two different signals. A1 points to the related exception data (from tc_ExceptData), and A6 contains the Exec library base. You can think of an exception as a subtask outside of your normal task. Because task exception code executes in &#039;&#039;user&#039;&#039; mode, however, the task stack must be large enough to supply the extra space consumed during an exception.&lt;br /&gt;
&lt;br /&gt;
While processing a given exception, Exec prevents that exception from occurring recursively. At exit from your exception-processing code, you should make sure D0 contains the signal mask the exception routine received in D0 because Exec looks here to see which signals it should reactivate. When the task executes the RTS instruction at the end of the exception routine, the system restores the previous contents of all of the task registers and resumes the task at the point where it was interrupted by the exception signal.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Exceptions Are Tricky&#039;&#039;. Exceptions are difficult to use safely. An exception can interrupt a task that is executing a critical section of code within a system function, or one that has locked a system resource such as the disk or blitter (note that even simple text output uses the blitter.) This possibility makes it dangerous to use most system functions within an exception unless you are sure that your interrupted task was performing only local, non-critical operations.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Task Traps ==&lt;br /&gt;
&lt;br /&gt;
Task &#039;&#039;traps&#039;&#039; are synchronous exceptions to the normal flow of program control. They are always generated as a direct result of an operation performed by your program&#039;s code. Whether they are accidental or purposely generated, they will result in your program being forced into a special condition in which it must immediately handle the trap. Address error, privilege violation, zero divide, and trap instructions all result in task traps. They may be generated directly by the &#039;&#039;68000&#039;&#039; processor (Motorola calls them &amp;quot;exceptions&amp;quot;) or simulated by software.&lt;br /&gt;
&lt;br /&gt;
A task that incurs a trap has no choice but to respond immediately. The task must have a module of code to handle the trap. Your task may be aborted if a trap occurs and no means of handling it has been provided. Default trap handling code (tc_TrapCode) is provided by the OS. You may instead choose to do your own processing of traps. The tc_TrapCode field is the address of the handler that you have designed to process the trap. The tc_TrapData field is the address of the data area for use by the trap handler.&lt;br /&gt;
&lt;br /&gt;
The system&#039;s default trap handling code generally displays a Software Error Requester or Alert containing an exception number and the program counter or task address. Processor exceptions generally have numbers in the range hex 00 to 2F. The &#039;&#039;68000&#039;&#039; processor exceptions of particular interest are as follows.&lt;br /&gt;
&lt;br /&gt;
Traps (68000 Exception Vector Numbers)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 2 || Bus error || access of nonexistent memory&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Address error || long/word access of odd address (&#039;&#039;68000&#039;&#039;)&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Illegal instruction || illegal opcode (other than Axxx or Fxxx)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Zero divide || processor division by zero&lt;br /&gt;
|-&lt;br /&gt;
| 6 || CHK instruction || register bounds error trap by CHK&lt;br /&gt;
|-&lt;br /&gt;
| 7 || TRAPV instruction || overflow error trap by TRAPV&lt;br /&gt;
|-&lt;br /&gt;
| 8 || Privilege violation || user execution of supervisor opcode&lt;br /&gt;
|-&lt;br /&gt;
| 9 || Trace || status register TRACE bit trap&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Line 1010 emulator || execution of opcode beginning with $A&lt;br /&gt;
|-&lt;br /&gt;
| 11 || Line 1111 emulator || execution of opcode beginning with $F&lt;br /&gt;
|-&lt;br /&gt;
| 32-47 || Trap instructions || TRAP &#039;&#039;N&#039;&#039; instruction where &#039;&#039;N&#039;&#039; = 0 to 15&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A system alert for a processor exception may set the high bit of the longword exception number to indicate an unrecoverable error (for example $8000¬†0005 for an unrecoverable processor exception #5). System alerts with more complex numbers are generally Amiga-specific software failures. These are built from the definitions in the &amp;amp;lt;exec/alerts.h&amp;amp;gt; include file.&lt;br /&gt;
&lt;br /&gt;
The actual stack frames generated for these traps are processor-dependent. The &#039;&#039;68010&#039;&#039;, &#039;&#039;68020&#039;&#039;, and &#039;&#039;68030&#039;&#039; processors will generate a different type of stack frame than the &#039;&#039;68000&#039;&#039;. If you plan on having your program handle its own traps, you should not make assumptions about the format of the supervisor stack frame. Check the flags in the AttnFlags field of the ExecBase structure for the type of processor in use and process the stack frame accordingly.&lt;br /&gt;
&lt;br /&gt;
=== Trap Handlers ===&lt;br /&gt;
&lt;br /&gt;
For compatibility with the &#039;&#039;68000&#039;&#039;, Exec performs trap handling in supervisor mode. This means that all task switching is disabled during trap handling.&lt;br /&gt;
&lt;br /&gt;
At entry to the task&#039;s trap handler, the system stack contains a processor-dependent trap frame as defined in the &#039;&#039;68000&#039;&#039;/&#039;&#039;10&#039;&#039;/&#039;&#039;20&#039;&#039;/&#039;&#039;30&#039;&#039; manuals. A longword exception number is added to this frame. That is, when a handler gains control, the top of stack contains the exception number and the trap frame immediately follows.&lt;br /&gt;
&lt;br /&gt;
To return from trap processing, remove the exception number from the stack (note that this is the supervisor stack, not the user stack) and then perform a return from exception (RTE).&lt;br /&gt;
&lt;br /&gt;
Because trap processing takes place in supervisor mode, with task dispatching disabled, it is strongly urged that you keep trap processing as short as possible or switch back to user mode from within your trap handler. If a trap handler already exists when you add your own trap handler, it is smart to propagate any traps that you do not handle down to the previous handler. This can be done by saving the previous address from tc_TrapCode and having your handler pass control to that address if the trap which occurred is not one you wish to handle.&lt;br /&gt;
&lt;br /&gt;
The following example installs a simple trap handler which intercepts processor divide-by-zero traps, and passes on all other traps to the previous default trap code. The example has two code modules which are linked together. The trap handler code is in assembler. The C module installs the handler, demonstrates its effectiveness, then restores the previous tc_TrapCode.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* trap_c.c - Execute me to compile me with SAS C 5.10&lt;br /&gt;
LC -b0 -cfistq -v -y -j73 trap_c.c&lt;br /&gt;
Blink FROM LIB:c.o,trap_c.o,trap_a.o TO trap LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit&lt;br /&gt;
&lt;br /&gt;
trap_c.c - C module of sample integer divide-by-zero trap&lt;br /&gt;
*/&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;exec/tasks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdlib.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;stdio.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE&lt;br /&gt;
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */&lt;br /&gt;
int chkabort(void) {return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
extern ULONG trapa();           /* assembler trap code in trap_a.asm */&lt;br /&gt;
&lt;br /&gt;
APTR oldTrapCode;&lt;br /&gt;
ULONG countdiv0;&lt;br /&gt;
&lt;br /&gt;
void main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
    struct Task *thistask;&lt;br /&gt;
    ULONG k,j;&lt;br /&gt;
&lt;br /&gt;
    thistask = FindTask(NULL);&lt;br /&gt;
&lt;br /&gt;
    /* Save our task&#039;s current trap code pointer */&lt;br /&gt;
    oldTrapCode = thistask-&amp;amp;gt;tc_TrapCode;&lt;br /&gt;
&lt;br /&gt;
    /* Point task to our assembler trap handler code.  Ours will just count */&lt;br /&gt;
    /* divide-by-zero traps, and pass other traps on to the normal TrapCode */&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = (APTR)trapa;&lt;br /&gt;
&lt;br /&gt;
    countdiv0 = 0L;&lt;br /&gt;
&lt;br /&gt;
    for(k=0; k&amp;amp;lt;4; k++)            /* Let&#039;s divide by zero a few times */&lt;br /&gt;
       {&lt;br /&gt;
       printf(&amp;amp;quot;dividing %ld by zero... &amp;amp;quot;,k);&lt;br /&gt;
       j = k/0L;&lt;br /&gt;
       printf(&amp;amp;quot;did it\n&amp;amp;quot;);&lt;br /&gt;
       }&lt;br /&gt;
    printf(&amp;amp;quot;\nDivide by zero happened %ld times\n&amp;amp;quot;,countdiv0);&lt;br /&gt;
&lt;br /&gt;
    thistask-&amp;amp;gt;tc_TrapCode = oldTrapCode;     /* Restore old trap code */&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;* trap_a.asm - Example trap handling code (leaves D0 intact).  Entered&lt;br /&gt;
* in supervisor mode with the following on the supervisor stack:&lt;br /&gt;
*    0(sp).l = trap#&lt;br /&gt;
*    4(sp) Processor dependent exception frame&lt;br /&gt;
&lt;br /&gt;
    INCLUDE &amp;amp;quot;exec/types.i&amp;amp;quot;&lt;br /&gt;
    INCLUDE &amp;amp;quot;libraries/dos.i&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
        XDEF _trapa&lt;br /&gt;
        XREF _countdiv0&lt;br /&gt;
        XREF _oldTrapCode&lt;br /&gt;
&lt;br /&gt;
        CODE&lt;br /&gt;
_trapa:                                 ; our trap handler entry&lt;br /&gt;
        CMPI.L  #5,(SP)                 ; is this a divide by zero ?&lt;br /&gt;
        BNE.S   notdiv0                 ; no&lt;br /&gt;
        ADD.L   #1,_countdiv0           ; yes, increment our div0 count&lt;br /&gt;
endtrap:&lt;br /&gt;
        ADDQ    #4,SP                   ; remove exception number from SSP&lt;br /&gt;
        RTE                             ; return from exception&lt;br /&gt;
notdiv0:&lt;br /&gt;
        TST.L   _oldTrapCode            ; is there another trap handler ?&lt;br /&gt;
        BEQ.S   endtrap                 ; no, so we&#039;ll exit&lt;br /&gt;
        MOVE.L  _oldTrapCode,-(SP)      ; yes, go on to old TrapCode&lt;br /&gt;
        RTS                             ; jumps to old TrapCode&lt;br /&gt;
&lt;br /&gt;
        END&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Trap Instructions ===&lt;br /&gt;
&lt;br /&gt;
The TRAP instructions in the &#039;&#039;68000&#039;&#039; generate traps 32-47. Because many independent pieces of system code may desire to use these traps, the AllocTrap() and FreeTrap() functions are provided. These work in a fashion similar to that used by AllocSignal() and FreeSignal(), mentioned above.&lt;br /&gt;
&lt;br /&gt;
Allocating a trap is simply a bookkeeping job within a task. It does not affect how the system calls the trap handler; it helps coordinate who owns what traps. Exec does nothing to determine whether or not a task is prepared to handle a particular trap. It simply calls your code. It is up to your program to handle the trap.&lt;br /&gt;
&lt;br /&gt;
To allocate any trap, you can use the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(-1)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;all trap instructions are in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can select a specific trap using this code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
if (-1 == (trap = IExec-&amp;gt;AllocTrap(3)))&lt;br /&gt;
    IDOS-&amp;gt;Printf(&amp;quot;trap #3 is in use\n&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To free a trap, you use the FreeTrap() function passing it the trap number to be freed.&lt;br /&gt;
&lt;br /&gt;
== Processor and Cache Control ==&lt;br /&gt;
&lt;br /&gt;
Exec provides a number of to control the processor mode and, if available, the caches. All these functions work independently of the specific &#039;&#039;M68000&#039;&#039; family processor type. This enables you to write code which correctly controls the state of both the &#039;&#039;MC68000&#039;&#039; and the &#039;&#039;MC68040&#039;&#039;. Along with processor mode and cache control, functions are provided to obtain information about the condition code register (CCR) and status register (SR). No functions are provided to control a paged memory management unit (PMMU) or floating point unit (FPU).&lt;br /&gt;
&lt;br /&gt;
Processor and Cache Control Functions&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| GetCC() || Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR() || Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState() || Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor() || Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState() || Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE() || Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU() || Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl() || Global cache control.&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA() || Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA() || Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Supervisor Mode ===&lt;br /&gt;
&lt;br /&gt;
While in supervisor mode, you have complete access to all data and registers, including those used for task scheduling and exceptions, and can execute privileged instructions. In application programs, normally only task trap code is directly executed in supervisor mode, to be compatible with the &#039;&#039;MC68000&#039;&#039;. For normal applications, it should never be necessary to switch to supervisor mode itself, only indirectly through Exec function calls. Remember that task switching is disabled while in supervisor mode. If it is absolutely needed to execute code in supervisor mode, keep it as brief as possible.&lt;br /&gt;
&lt;br /&gt;
Supervisor mode can only be entered when a &#039;&#039;680x0&#039;&#039; exception occurs (an interrupt or trap). The Supervisor() function allows you to trap an exception to a specified assembly function. In this function your have full access to all registers. No registers are saved when your function is invoked. You are responsible for restoring the system to a sane state when you are done. You must return to user mode with an RTE instruction. You must not return to user mode by executing a privileged instruction which clears the supervisor bit in the status register. Refer to a manual on the &#039;&#039;M68000&#039;&#039; family of CPUs for information about supervisor mode and available privileged instructions per processor type.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;MC68000&#039;&#039; has two stacks, the user stack (USP) and supervisor stack (SSP). As of the &#039;&#039;MC68020&#039;&#039; there are two supervisor stacks, the interrupt stack pointer (ISP) and the master stack pointer (MSP). The SuperState() function allows you to enter supervisor mode with the USP used as SSP. The function returns the SSP, which will be the MSP, if an &#039;&#039;MC68020&#039;&#039; or greater is used. Returning to user mode is done with the UserState() function. This function takes the SSP as argument, which must be saved when SuperState() is called. Because of possible problems with stack size, Supervisor() is to be preferred over SuperState().&lt;br /&gt;
&lt;br /&gt;
=== Status Register ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The processor status register bits can be set or read with the SetSR() function. This function operates in supervisor mode, thus both the upper and lower byte of the SR can be read or set. Be very sure you know what you are doing when you use this function to set bits in the SR and above all never try to use this function to enter supervisor mode. Refer to the &#039;&#039;M68000 Programmers Reference Manual&#039;&#039; by Motorola Inc. for information about the definition of individual SR bits per processor type.&lt;br /&gt;
&lt;br /&gt;
=== Condition Code Register ===&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;MC68000&#039;&#039; a copy of the processor condition codes can be obtained with the MOVE SR,&amp;amp;lt;ea&amp;amp;gt; instruction. On &#039;&#039;MC68010&#039;&#039; processors and up however, the instruction MOVE CCR,&amp;amp;lt;ea&amp;amp;gt; must be used. Using the specific &#039;&#039;MC68000&#039;&#039; instruction on later processors will cause a &#039;&#039;680x0&#039;&#039; exception since it is a privileged instruction on those processors. The GetCC() function provides a processor independent way of obtaining a copy of the condition codes. For all processors there are 5 bits which can indicate the result of an integer or a system control instruction:&lt;br /&gt;
&lt;br /&gt;
* X - extend&lt;br /&gt;
* N - negative&lt;br /&gt;
* Z - zero&lt;br /&gt;
* V - overflow&lt;br /&gt;
* C - carry&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;X&#039;&#039;&#039; bit is used for multiprecision calculations. If used, it is copy of the carry bit. The other bits state the result of a processor operation.&lt;br /&gt;
&lt;br /&gt;
=== Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
As of the &#039;&#039;MC68020&#039;&#039; all processors have an instruction cache, 256 bytes on the &#039;&#039;MC68020&#039;&#039; and &#039;&#039;MC68030&#039;&#039; and 4 KBytes on a &#039;&#039;MC68040&#039;&#039;. The &#039;&#039;MC68030&#039;&#039; and &#039;&#039;MC68040&#039;&#039; have data caches as well, 256 bytes and 4 KBytes respectively. All the processors load instructions ahead of the program counter (PC), albeit it that the &#039;&#039;MC68000&#039;&#039; and &#039;&#039;MC68010&#039;&#039; only prefetch one and two words respectively. This means the CPU loads instructions ahead of the current program counter. For this reason self-modifying code is strongly discouraged. If your code modifies or decrypts itself just ahead of the program counter, the pre-fetched instructions may not match the modified instructions. If self-modifying code must be used, flushing the cache is the safest way to prevent this.&lt;br /&gt;
&lt;br /&gt;
=== DMA Cache Functions ===&lt;br /&gt;
&lt;br /&gt;
The CachePreDMA() and CachePostDMA() functions allow you to flush the data cache before and after Direct Memory Access. Typically only DMA device drivers benefit from this. These functions take the processor type, possible MMU and cache mode into account. When no cache is available they end up doing nothing. These functions can be replaced with ones suitable for different cache hardware. Refer to the SDK for implementation specifics.&lt;br /&gt;
&lt;br /&gt;
Since DMA device drivers read and write directly to memory, they are effected by the CopyBack feature of the &#039;&#039;MC68040&#039;&#039; (explained below). Using DMA with CopyBack mode requires a cache flush. If a DMA device needs to read RAM via DMA, it must make sure that the data in the caches has been written to memory first, by calling CachePreDMA(). In case of a write to memory, the DMA device should first clear the caches with CachePreDMA(), write the data and flush the caches again with CachePostDMA().&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following chart gives a brief description of the Exec functions that control tasks. See the SDK for details about each call.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Exec Task Function&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| AddTask()&lt;br /&gt;
| Add a task to the system.&lt;br /&gt;
|-&lt;br /&gt;
| AllocTrap()&lt;br /&gt;
| Allocate a processor trap vector.&lt;br /&gt;
|-&lt;br /&gt;
| Disable()&lt;br /&gt;
| Disable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| Enable()&lt;br /&gt;
| Enable interrupt processing.&lt;br /&gt;
|-&lt;br /&gt;
| FindTask()&lt;br /&gt;
| Find a specific task.&lt;br /&gt;
|-&lt;br /&gt;
| Forbid()&lt;br /&gt;
| Forbid task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| FreeTrap()&lt;br /&gt;
| Release a process trap.&lt;br /&gt;
|-&lt;br /&gt;
| Permit()&lt;br /&gt;
| Permit task rescheduling.&lt;br /&gt;
|-&lt;br /&gt;
| SetTaskPri()&lt;br /&gt;
| Set the priority of a task.&lt;br /&gt;
|-&lt;br /&gt;
| RemTask()&lt;br /&gt;
| Remove a task from the system.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearE()&lt;br /&gt;
| Flush CPU instruction and/or data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheClearU()&lt;br /&gt;
| Flush CPU instruction and data caches.&lt;br /&gt;
|-&lt;br /&gt;
| CacheControl()&lt;br /&gt;
| Global cache control (V37).&lt;br /&gt;
|-&lt;br /&gt;
| CachePostDMA()&lt;br /&gt;
| Perform actions prior to hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| CachePreDMA()&lt;br /&gt;
| Perform actions after hardware DMA.&lt;br /&gt;
|-&lt;br /&gt;
| GetCC()&lt;br /&gt;
| Get processor condition codes.&lt;br /&gt;
|-&lt;br /&gt;
| SetSR()&lt;br /&gt;
| Get/set processor status register.&lt;br /&gt;
|-&lt;br /&gt;
| SuperState()&lt;br /&gt;
| Set supervisor mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| Supervisor()&lt;br /&gt;
| Execute a short supervisor mode function.&lt;br /&gt;
|-&lt;br /&gt;
| UserState()&lt;br /&gt;
| Return to user mode with user stack.&lt;br /&gt;
|-&lt;br /&gt;
| CreateTask()&lt;br /&gt;
| Setup and add a new task.&lt;br /&gt;
|-&lt;br /&gt;
| DeleteTask()&lt;br /&gt;
| Delete a task created with CreateTask().&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3392</id>
		<title>Introduction to Exec</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3392"/>
		<updated>2012-07-08T11:04:32Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Libraries and Devices */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to Exec ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;Multitasking Executive&#039;&#039;, better known as &#039;&#039;Exec&#039;&#039;, is the heart of the Amiga&#039;s operating system. All other systems in the Amiga rely on it to control multitasking, to manage the message-based interprocess communications system, and to arbitrate access to system resources. Because just about every software entity on the Amiga (including application programs) needs to use Exec in some way, every Amiga programmer has to have a basic understanding of its fundamentals.&lt;br /&gt;
&lt;br /&gt;
== Multitasking ==&lt;br /&gt;
&lt;br /&gt;
A conventional micro-computer spends a lot of its time waiting for things to happen. It has to wait for such things as the user to push buttons on the keyboard or mouse, for data to come in through the serial port, and for data to go out to a disk drive. To make efficient use of the CPU&#039;s time, an operating system can have the CPU carry out some other task while it is waiting for such events to occur.&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;multitasking&#039;&#039; operating system reduces the amount of time it wastes, by switching to another program when the current one needs to wait for an event. A multitasking operating system can have several programs, or tasks, running at the same time. Each task runs independently of the others, without having to worry about what the other tasks are doing. From a task&#039;s point of view, it&#039;s as if each task has a computer all to itself.&lt;br /&gt;
&lt;br /&gt;
The Amiga&#039;s multitasking works by switching which task is currently using the CPU. A task can be a user&#039;s application program, or it can be a task which controls system resources (like the disk drives or the keyboard). Each task has a priority assigned to it. Exec will let the task with the highest priority use the CPU, but only if the task is ready to run. A task can be in one of three states: &#039;&#039;ready&#039;&#039;, &#039;&#039;sleeping&#039;&#039;, or &#039;&#039;running&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A ready task is not currently using the CPU but is waiting to use the processor. Exec keeps a list of the tasks that are ready. Exec sorts this list according to task priority, so Exec can easily find the ready task with the highest priority. When Exec switches the task that currently has control of the CPU, it switches to the task at the top of this list.&lt;br /&gt;
&lt;br /&gt;
A sleeping task is not currently running and is waiting for some event to happen. When that event occurs, Exec will move the sleeping task into the list of ready tasks.&lt;br /&gt;
&lt;br /&gt;
A running task is currently using the CPU. It will remain the current task until one of three things occur:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;A higher priority task becomes ready, so the OS preempts the current task and switches to the higher priority task.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task needs to wait for an event, so it goes to sleep and Exec switches to the highest priority task in Exec&#039;s ready list.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task has had control of the CPU for at least a preset time period called a &#039;&#039;quantum&#039;&#039; and there is another task of equal priority ready to run. In this case, Exec will preempt the current task for the ready one with the same priority. This is known as &#039;&#039;time-slicing&#039;&#039;. When there is a group of tasks of equal priority on the top of the ready list, Exec will cycle through them, letting each one use the CPU for a quantum (a slice of time).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The terms &amp;quot;task&amp;quot; and &amp;quot;process&amp;quot; are often used interchangeably to represent the generic concept of task. On the Amiga, this terminology can be a little confusing because of the names of the data structures that are associated with Exec tasks. Each task has a structure associated with it called a Task structure (defined in &amp;amp;lt;exec/tasks.h&amp;amp;gt;). Most application tasks use a superset of the Task structure called a Process structure (defined in &amp;amp;lt;dos/dosextens.h&amp;amp;gt;). These terms are confusing to Amiga programmers because there is an important distinction between the Exec task with only a Task structure and an Exec task with a Process structure.&lt;br /&gt;
&lt;br /&gt;
The Process structure builds on the Task structure and contains some extra fields which allow the DOS library to associate an AmigaDOS environment to the task. Some elements of a DOS environment include a current input and output stream and a current working directory. These elements are important to applications that need to do standard input and output using functions like printf().&lt;br /&gt;
&lt;br /&gt;
Exec only pays attention to the Task structure portion of the Process structure, so, as far as Exec is concerned, there is no difference between a task with a Task structure and a task with a Process structure. Exec considers both of them to be tasks.&lt;br /&gt;
&lt;br /&gt;
An application doesn&#039;t normally worry about which structure their task uses. Instead, the system that launches the application takes care of it. Both Workbench and the Shell (CLI) attach a Process structure to the application tasks that they launch.&lt;br /&gt;
&lt;br /&gt;
== Dynamic Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
The Amiga has a soft machine architecture, meaning that all tasks, including those that are part of its operating system, do not use fixed memory addresses. As a result, any program that needs to use a chunk of memory must allocate that memory from the operating system.&lt;br /&gt;
&lt;br /&gt;
There is one function on the Amiga for simple memory allocation: AllocVecTags(). This function accept the a uint32 containing the size of the memory block in bytes followed by a TagItem array for memory attributes. The function returns the address of a memory block aligned to your specifications if successful or NULL if something went wrong.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any tags when allocating memory, the system looks for MEMF_PRIVATE memory. There are additional memory allocation tags: MEMF_SHARED and MEMF_EXECUTABLE. See the [[Exec_Memory_Allocation|Exec Memory Allocation]] section for additional information on attributes and tags.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Make Sure You Have Memory.&#039;&#039; Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
FreeVec() releases memory allocated by AllocVecTags(). It takes only one parameter, a pointer to a memory block allocated by AllocVecTags(). The following example shows how to allocate and deallocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR my_mem = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (my_mem != NULL)&lt;br /&gt;
{&lt;br /&gt;
    /* Your code goes here */&lt;br /&gt;
    IExec-&amp;gt;FreeVec(my_mem);&lt;br /&gt;
}&lt;br /&gt;
else  { /* couldn&#039;t get memory, exit with an error */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Signals ==&lt;br /&gt;
&lt;br /&gt;
The Amiga uses a mechanism called &#039;&#039;signals&#039;&#039; to tell a task that some event occurred. Each task has its own set of 32 signals, 16 of which are set aside for system use. When one task signals a second task, it asks the OS to set a specific bit in the 32-bit long word set aside for the second task&#039;s signals.&lt;br /&gt;
&lt;br /&gt;
Signals are what makes it possible for a task to go to sleep. When a task goes to sleep, it asks the OS to wake it up when a specific signal bit gets set. That bit is tied to some event. When that event occurs, that signal bit gets set. This triggers the OS into waking up the sleeping task.&lt;br /&gt;
&lt;br /&gt;
To go to sleep, a task calls a system function called Wait(). This function takes one argument, a bitmask that tells Exec which of the task&#039;s signal bits to &amp;quot;listen to&amp;quot;. The task will only wake up if it receives one of the signals whose corresponding bit is set in that bitmask. For example, if a task wanted wait for signals 17 and 19, it would call Wait() like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
mysignals = IExec-&amp;gt;Wait(1U &amp;lt;&amp;lt; 17 | 1U &amp;lt;&amp;lt; 19);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wait() puts the task to sleep and will not return until some other task sets at least one of these two signals. When the task wakes up, mysignals will contain the bitmask of the signal or signals that woke up the task. It is possible for several signals to occur simultaneously, so any combination of the signals that the task Wait()ed on can occur. It is up to the waking task to use the return value from Wait() to figure out which signal or signals occurred.&lt;br /&gt;
&lt;br /&gt;
More information on signals can be found in [[Exec_Signals|Exec Signals]].&lt;br /&gt;
&lt;br /&gt;
== Interprocess Communications ==&lt;br /&gt;
&lt;br /&gt;
Another feature of the Amiga OS is its system of message-based &#039;&#039;interprocess communication&#039;&#039;. Using this system, a task can send a message to a message port owned by another task. Tasks use this mechanism to do things like trigger events or share data with other tasks, including system tasks. Exec&#039;s message system is built on top of Exec&#039;s task signaling mechanism. Most Amiga applications programming (especially Intuition programming) relies heavily upon this message-based form of interprocess communication.&lt;br /&gt;
&lt;br /&gt;
When one task sends a message to another task&#039;s message port, the OS adds the message to the port&#039;s message queue. The message stays in this queue until the task that owns the port is ready to check its port for messages. Typically, a task has put itself to sleep while it is waiting for an event, like a message to arrive at its message port. When the message arrives, the task wakes up to look in its message port. The messages in the message port&#039;s queue are arranged in first-in-first-out (FIFO) order so that, when a task receives several messages, it will see the messages in the order they arrived at the&lt;br /&gt;
&lt;br /&gt;
A task can use a message to share any kind of data with another task. &#039;&#039;&#039;This is possible because a task does not actually transmit an entire message, it only passes a pointer to a message. When a task creates a message (which can have many Kilobytes of data attached to it) and sends it to another task, the actual message does not move.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Essentially, &#039;&#039;&#039;when &#039;&#039;task A&#039;&#039; sends a message to &#039;&#039;task B&#039;&#039;, &#039;&#039;task A&#039;&#039; lends &#039;&#039;task B&#039;&#039; a chunk of its memory, the memory occupied by the message. After &#039;&#039;task A&#039;&#039; sends the message, it has relinquished that memory to &#039;&#039;task B&#039;&#039;, so it cannot touch the memory occupied by the message. &#039;&#039;Task B&#039;&#039; has control of that memory until &#039;&#039;task B&#039;&#039; returns the message to &#039;&#039;task A&#039;&#039; with the ReplyMsg() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Let&#039;s look at an example. Many applications use Intuition windows as a source for user input. Without getting into too much detail about Intuition, when an application opens a window, it can set up the window so Intuition will send messages about certain user input events. Intuition sends these messages to a message port created by Intuition for use with this window. When an application successfully opens a window, it receives a pointer to a Window structure, which contains a pointer to this message port (Window.UserPort). For this example, we&#039;ll assume the window has been set up so Intuition will send a message only if the user clicks the window&#039;s close gadget.&lt;br /&gt;
&lt;br /&gt;
When Intuition opens the window in this example, it creates a message port for the task that opened the Window. Because the most common message passing system uses signals, creating this message port involves using one of the example task&#039;s 32 signals. The OS uses this signal to signal the task when it receives a message at this message port. This allows the task to sleep while waiting for a &amp;quot;Close Window&amp;quot; event to arrive. Since this simple example is only waiting for activity at one message port, it can use the WaitPort() function. WaitPort() accepts a pointer to a message port and puts a task to sleep until a message arrives at that port.&lt;br /&gt;
&lt;br /&gt;
This simple example needs two variables, one to hold the address of the window and the other to hold the address of a message.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Message *mymsg; /*defined in &amp;lt;exec/ports.h&amp;gt; */&lt;br /&gt;
struct Window *mywin;  /* defined in &amp;lt;intuition/intuition.h&amp;gt; */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* at some point, this application would have successfully opened a */&lt;br /&gt;
/* window and stored a pointer to it in mywin.                      */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    /* Here the application goes to sleep until the user clicks the window&#039;s close    */&lt;br /&gt;
    /* gadget.  This window was set up so that the only time Intuition will send a    */&lt;br /&gt;
    /* message to this window&#039;s port is if the user clicks the window&#039;s close gadget. */&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;WaitPort(mywin-&amp;gt;UserPort);&lt;br /&gt;
    while (mymsg = IExec-&amp;gt;GetMsg(mywin-&amp;gt;UserPort))&lt;br /&gt;
    {&lt;br /&gt;
        /* process message now or copy information from message */&lt;br /&gt;
        IExec-&amp;gt;ReplyMsg(mymsg);&lt;br /&gt;
    }&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* Close window, clean up */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Exec function GetMsg() is used to extract messages from the message port. Since the memory for these messages belongs to Intuition, the example relinquishes each message as it finishes with them by calling ReplyMsg(). Notice that the example keeps on trying to get messages from the message port until mymsg is NULL. This is to make sure there are no messages left in the message port&#039;s message queue. It is possible for several messages to pile up in the message queue while the task is asleep, so the example has to make sure it replies to all of them. Note that the window should never be closed within this GetMsg() loop because the while statement is still accessing the window&#039;s UserPort.&lt;br /&gt;
&lt;br /&gt;
Note that each task with a Process structure (sometimes referred to as a process) has a special process message port, Process.pr_MsgPort. This message port is only for use by Workbench and the DOS library itself. &#039;&#039;No application should use this port for its own use!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
More detailed information about message ports can be found in [[Exec_Messages_and_Ports|Exec Messages and Ports]].&lt;br /&gt;
&lt;br /&gt;
==== Waiting on Message Ports and Signals at the Same Time ====&lt;br /&gt;
&lt;br /&gt;
Most applications need to wait for a variety of messages and signals from a variety of sources. For example, an application might be waiting for Window events and also timer.device messages. In this case, an application must Wait() on the combined signal bits of all signals it is interested in, including the signals for the message ports where any messages might arrive.&lt;br /&gt;
&lt;br /&gt;
The MsgPort structure, which is defined in &amp;amp;lt;exec/ports.h&amp;amp;gt;, is what Exec uses to keep track of a message port. The UserPort field from the example above points to one of these structures. In this structure is a field called mp_SigBit, which contains the &#039;&#039;number&#039;&#039; (not the actual bit mask) of the message port&#039;s signal bit. To Wait() on the signal of a message port, Wait() on a bit mask created by shifting 1U to the left mp_SigBit times (1U &amp;lt;&amp;lt; msgport-&amp;amp;gt;mp_SigBit). The resulting bit mask can be &#039;&#039;&#039;OR&#039;&#039;&#039;&#039;d with the bit masks for any other signals you wish to simultaneously wait on.&lt;br /&gt;
&lt;br /&gt;
== Libraries and Devices ==&lt;br /&gt;
&lt;br /&gt;
One of the design goals for the Amiga OS was to make the system dynamic enough so that the OS could be extended and updated without effecting existing applications. Another design goal was to make it easy for different applications to be able to share common pieces of code. The Amiga accomplished these goals through a system of libraries. An Amiga library consists of a collection of related functions which can be anywhere in system memory (RAM or ROM).&lt;br /&gt;
&lt;br /&gt;
Devices are very similar to libraries, except they usually control some sort of I/O device and contain some extra standard functions. Although this section does not really discuss devices directly, the material here applies to them. For more information on devices, see the [[Exec Device I/O]] section of this manual or the [[Devices|Devices]].&lt;br /&gt;
&lt;br /&gt;
An application accesses a library&#039;s functions through interfaces. Each library exports one or more interfaces. Before a task can use the functions of a particular interface, it must first acquire the library&#039;s base pointer by calling the Exec OpenLibrary() function and then obtain an interface pointer by calling GetInterface().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Library *OpenLibrary( CONST_STRPTR libName, uint32 libVer );&lt;br /&gt;
struct Interface *GetInterface( struct Library *base, CONST_STRPTR ifaceName, uint32 ifaceVer, struct TagItem *tags);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where libName is a string naming the library and libVer is a version number for the library. The library base pointer is passed on to GetInterface() along with ifaceName which is the name of the interface and ifaceVer which is the version. An optional tag array completes the call.&lt;br /&gt;
&lt;br /&gt;
The libVer number reflects a revision of the system software. The chart below lists the specific Amiga OS release versions that system libraries versions correspond to:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 27.3 || Kickstart 0.7 || First AmigaOS version shown at Consumer Electronics Show in 1985. This version is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 29 || Kickstart 0.9 || This is an internal AmigaOS version. This version is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 30 || Kickstart 1.0 || This revision is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 31 || Kickstart 1.1 || This was an NTSC only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 32 || Kickstart 1.1 || This was a PAL only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 33 || Kickstart 1.2 || This is the oldest revision of the OS.&lt;br /&gt;
|-&lt;br /&gt;
| 34 || Kickstart 1.3 || This is almost the same as release 1.2 except it has an Autobooting expansion.library&lt;br /&gt;
|-&lt;br /&gt;
| 35 || || This is a special RAM-loaded version of the 1.3 revision, except that it knows about the &#039;&#039;A2024&#039;&#039; display modes. No application should need this library unless they need to open an &#039;&#039;A2024&#039;&#039; display mode under 1.3.&lt;br /&gt;
|-&lt;br /&gt;
| 36 || Kickstart 2.0 || This is the original Release 2 revision that was initially shipped on early &#039;&#039;Amiga 3000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 37 || Kickstart 2.04 || This is the general Release 2 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 38 || Kickstart 2.1 || This is an updated Release 2 revision for &#039;&#039;Amiga 600&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 39 || Kickstart 3.0 || This is the original Release 3 revision hat was initially shipped on &#039;&#039;Amiga 1200&#039;&#039; and &#039;&#039;Amiga 4000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 40 || Kickstart 3.1 || This is the original Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 41 || Kickstart 3.1 || This is version number which was reserved for the Japanese locale version of AmigaOS 3.1, with multi-byte character support (never completed).&lt;br /&gt;
|-&lt;br /&gt;
| 42 ||  || This is version number for some AmigaOS&#039; modules of an internal alpha revision (early 1993).&lt;br /&gt;
|-&lt;br /&gt;
| 43 || Kickstart 3.2 || This is the original Release 3 revision for Amiga Walker model (never completed). This revision was used for stuff done by/for Amiga Technologies/Amiga International (1995).&lt;br /&gt;
|-&lt;br /&gt;
| 44 || Kickstart 3.5 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 45 || Kickstart 3.9 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 50 || Kickstart 4.0 || This is the general Release 4 revision for the new &#039;&#039;AmigaOne&#039;&#039; range (Developer PreRelease).&lt;br /&gt;
|-&lt;br /&gt;
| 51 || Kickstart 4.0 || This is an update to Release 4 revision for the new &#039;&#039;AmigaOne&#039;&#039; range (Official Release from Update 1).&lt;br /&gt;
|-&lt;br /&gt;
| 52 || Kickstart 4.0 || This is an update to Release 4 revision to include support for PPC equipped classic Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 53 || Kickstart 4.1 || This is an update to Release 4 revision for all supported PPC models.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The OpenLibrary() function looks for a library with a name that matches libName and also with a version at least as high as libVersion. For example, to open version 50 or greater of the Intuition library:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example, if version 50 or greater of the Intuition library is not available, OpenLibrary() returns NULL. A version of zero in OpenLibrary() tells the OS to open any version of the library. New code should use a version of 50 or higher unless noted otherwise.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;When OpenLibrary() looks for a library, it first looks in memory. If the library is not in memory, OpenLibrary() will look for the library on disk. If libName is a library name with an absolute path (for example, &amp;quot;myapp:mylibs/mylib.library&amp;quot;), OpenLibrary() will follow that absolute path looking for the library. If libName is only a library name (&amp;quot;diskfont.library&amp;quot;), OpenLibrary() will look for the library in the directory that the LIBS: logical assign currently references.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If OpenLibrary() finds the library on disk, it takes care of loading it and initializing it. As part of the initialization process, OpenLibrary() dynamically creates a vector table. There is a vector for each function in the library. The OS needs to create the vector table dynamically because the library functions can be anywhere in memory.&lt;br /&gt;
&lt;br /&gt;
After the library is loaded into memory and initialized, OpenLibrary() can actually &amp;quot;open&amp;quot; the library. It does this by calling the library&#039;s Open function vector. Every library has a standard vector set aside for an OPEN function so the library can set up any data or processes that it needs. Normally, a library&#039;s OPEN function increments its open count to keep track of how many tasks currently have the library opened.&lt;br /&gt;
&lt;br /&gt;
If any step of OpenLibrary() fails, it returns a NULL value. If OpenLibrary() is successful, it returns the address of the library base. The library base is the address of this library&#039;s Library structure (defined in &amp;quot;exec/libraries.h&amp;quot;). The Library structure immediately follows the vector table in memory.&lt;br /&gt;
&lt;br /&gt;
Once the library has been opened the application needs to obtain access to the interfaces with GetInterface() in order to call the functions available. Every task or process is required to call GetInterface() on the instance and should not share the interface pointer.&lt;br /&gt;
&lt;br /&gt;
After an application is finished with a library, it &#039;&#039;must&#039;&#039; close it by calling DropInterface() on each interface and finally CloseLibrary():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
VOID DropInterface(struct Interface *ifacePtr);&lt;br /&gt;
VOID CloseLibrary(struct Library *libPtr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where ifacePtr is a pointer to an interface and libPtr is a pointer to the library base returned when the library was opened with OpenLibrary().&lt;br /&gt;
&lt;br /&gt;
For more detailed information about libraries and interfaces see [[Exec_Libraries|Exec Libraries]].&lt;br /&gt;
&lt;br /&gt;
=== Calling a Library Function ===&lt;br /&gt;
&lt;br /&gt;
To call a function in an Amiga system library a pointer to an interface is required. For example to call the Intuition function DisplayBeep():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IntuitionIFace *IIntuition = IExec-&amp;gt;GetInterface(base, &amp;quot;main&amp;quot; 1, NULL);&lt;br /&gt;
&lt;br /&gt;
IIntuition-&amp;gt;DisplayBeep();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Function parameters in C are pushed on the stack when a program calls a function. The order in which parameters are pushed is defined by the [http://en.wikipedia.org/wiki/Executable_and_Linkable_Format SysV ABI specification].&lt;br /&gt;
&lt;br /&gt;
For more detailed information about calling library functions see [[Exec_Libraries|Exec Libraries]].&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3391</id>
		<title>Introduction to Exec</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3391"/>
		<updated>2012-07-08T10:45:00Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Libraries and Devices */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to Exec ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;Multitasking Executive&#039;&#039;, better known as &#039;&#039;Exec&#039;&#039;, is the heart of the Amiga&#039;s operating system. All other systems in the Amiga rely on it to control multitasking, to manage the message-based interprocess communications system, and to arbitrate access to system resources. Because just about every software entity on the Amiga (including application programs) needs to use Exec in some way, every Amiga programmer has to have a basic understanding of its fundamentals.&lt;br /&gt;
&lt;br /&gt;
== Multitasking ==&lt;br /&gt;
&lt;br /&gt;
A conventional micro-computer spends a lot of its time waiting for things to happen. It has to wait for such things as the user to push buttons on the keyboard or mouse, for data to come in through the serial port, and for data to go out to a disk drive. To make efficient use of the CPU&#039;s time, an operating system can have the CPU carry out some other task while it is waiting for such events to occur.&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;multitasking&#039;&#039; operating system reduces the amount of time it wastes, by switching to another program when the current one needs to wait for an event. A multitasking operating system can have several programs, or tasks, running at the same time. Each task runs independently of the others, without having to worry about what the other tasks are doing. From a task&#039;s point of view, it&#039;s as if each task has a computer all to itself.&lt;br /&gt;
&lt;br /&gt;
The Amiga&#039;s multitasking works by switching which task is currently using the CPU. A task can be a user&#039;s application program, or it can be a task which controls system resources (like the disk drives or the keyboard). Each task has a priority assigned to it. Exec will let the task with the highest priority use the CPU, but only if the task is ready to run. A task can be in one of three states: &#039;&#039;ready&#039;&#039;, &#039;&#039;sleeping&#039;&#039;, or &#039;&#039;running&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A ready task is not currently using the CPU but is waiting to use the processor. Exec keeps a list of the tasks that are ready. Exec sorts this list according to task priority, so Exec can easily find the ready task with the highest priority. When Exec switches the task that currently has control of the CPU, it switches to the task at the top of this list.&lt;br /&gt;
&lt;br /&gt;
A sleeping task is not currently running and is waiting for some event to happen. When that event occurs, Exec will move the sleeping task into the list of ready tasks.&lt;br /&gt;
&lt;br /&gt;
A running task is currently using the CPU. It will remain the current task until one of three things occur:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;A higher priority task becomes ready, so the OS preempts the current task and switches to the higher priority task.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task needs to wait for an event, so it goes to sleep and Exec switches to the highest priority task in Exec&#039;s ready list.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task has had control of the CPU for at least a preset time period called a &#039;&#039;quantum&#039;&#039; and there is another task of equal priority ready to run. In this case, Exec will preempt the current task for the ready one with the same priority. This is known as &#039;&#039;time-slicing&#039;&#039;. When there is a group of tasks of equal priority on the top of the ready list, Exec will cycle through them, letting each one use the CPU for a quantum (a slice of time).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The terms &amp;quot;task&amp;quot; and &amp;quot;process&amp;quot; are often used interchangeably to represent the generic concept of task. On the Amiga, this terminology can be a little confusing because of the names of the data structures that are associated with Exec tasks. Each task has a structure associated with it called a Task structure (defined in &amp;amp;lt;exec/tasks.h&amp;amp;gt;). Most application tasks use a superset of the Task structure called a Process structure (defined in &amp;amp;lt;dos/dosextens.h&amp;amp;gt;). These terms are confusing to Amiga programmers because there is an important distinction between the Exec task with only a Task structure and an Exec task with a Process structure.&lt;br /&gt;
&lt;br /&gt;
The Process structure builds on the Task structure and contains some extra fields which allow the DOS library to associate an AmigaDOS environment to the task. Some elements of a DOS environment include a current input and output stream and a current working directory. These elements are important to applications that need to do standard input and output using functions like printf().&lt;br /&gt;
&lt;br /&gt;
Exec only pays attention to the Task structure portion of the Process structure, so, as far as Exec is concerned, there is no difference between a task with a Task structure and a task with a Process structure. Exec considers both of them to be tasks.&lt;br /&gt;
&lt;br /&gt;
An application doesn&#039;t normally worry about which structure their task uses. Instead, the system that launches the application takes care of it. Both Workbench and the Shell (CLI) attach a Process structure to the application tasks that they launch.&lt;br /&gt;
&lt;br /&gt;
== Dynamic Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
The Amiga has a soft machine architecture, meaning that all tasks, including those that are part of its operating system, do not use fixed memory addresses. As a result, any program that needs to use a chunk of memory must allocate that memory from the operating system.&lt;br /&gt;
&lt;br /&gt;
There is one function on the Amiga for simple memory allocation: AllocVecTags(). This function accept the a uint32 containing the size of the memory block in bytes followed by a TagItem array for memory attributes. The function returns the address of a memory block aligned to your specifications if successful or NULL if something went wrong.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any tags when allocating memory, the system looks for MEMF_PRIVATE memory. There are additional memory allocation tags: MEMF_SHARED and MEMF_EXECUTABLE. See the [[Exec_Memory_Allocation|Exec Memory Allocation]] section for additional information on attributes and tags.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Make Sure You Have Memory.&#039;&#039; Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
FreeVec() releases memory allocated by AllocVecTags(). It takes only one parameter, a pointer to a memory block allocated by AllocVecTags(). The following example shows how to allocate and deallocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR my_mem = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (my_mem != NULL)&lt;br /&gt;
{&lt;br /&gt;
    /* Your code goes here */&lt;br /&gt;
    IExec-&amp;gt;FreeVec(my_mem);&lt;br /&gt;
}&lt;br /&gt;
else  { /* couldn&#039;t get memory, exit with an error */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Signals ==&lt;br /&gt;
&lt;br /&gt;
The Amiga uses a mechanism called &#039;&#039;signals&#039;&#039; to tell a task that some event occurred. Each task has its own set of 32 signals, 16 of which are set aside for system use. When one task signals a second task, it asks the OS to set a specific bit in the 32-bit long word set aside for the second task&#039;s signals.&lt;br /&gt;
&lt;br /&gt;
Signals are what makes it possible for a task to go to sleep. When a task goes to sleep, it asks the OS to wake it up when a specific signal bit gets set. That bit is tied to some event. When that event occurs, that signal bit gets set. This triggers the OS into waking up the sleeping task.&lt;br /&gt;
&lt;br /&gt;
To go to sleep, a task calls a system function called Wait(). This function takes one argument, a bitmask that tells Exec which of the task&#039;s signal bits to &amp;quot;listen to&amp;quot;. The task will only wake up if it receives one of the signals whose corresponding bit is set in that bitmask. For example, if a task wanted wait for signals 17 and 19, it would call Wait() like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
mysignals = IExec-&amp;gt;Wait(1U &amp;lt;&amp;lt; 17 | 1U &amp;lt;&amp;lt; 19);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wait() puts the task to sleep and will not return until some other task sets at least one of these two signals. When the task wakes up, mysignals will contain the bitmask of the signal or signals that woke up the task. It is possible for several signals to occur simultaneously, so any combination of the signals that the task Wait()ed on can occur. It is up to the waking task to use the return value from Wait() to figure out which signal or signals occurred.&lt;br /&gt;
&lt;br /&gt;
More information on signals can be found in [[Exec_Signals|Exec Signals]].&lt;br /&gt;
&lt;br /&gt;
== Interprocess Communications ==&lt;br /&gt;
&lt;br /&gt;
Another feature of the Amiga OS is its system of message-based &#039;&#039;interprocess communication&#039;&#039;. Using this system, a task can send a message to a message port owned by another task. Tasks use this mechanism to do things like trigger events or share data with other tasks, including system tasks. Exec&#039;s message system is built on top of Exec&#039;s task signaling mechanism. Most Amiga applications programming (especially Intuition programming) relies heavily upon this message-based form of interprocess communication.&lt;br /&gt;
&lt;br /&gt;
When one task sends a message to another task&#039;s message port, the OS adds the message to the port&#039;s message queue. The message stays in this queue until the task that owns the port is ready to check its port for messages. Typically, a task has put itself to sleep while it is waiting for an event, like a message to arrive at its message port. When the message arrives, the task wakes up to look in its message port. The messages in the message port&#039;s queue are arranged in first-in-first-out (FIFO) order so that, when a task receives several messages, it will see the messages in the order they arrived at the&lt;br /&gt;
&lt;br /&gt;
A task can use a message to share any kind of data with another task. &#039;&#039;&#039;This is possible because a task does not actually transmit an entire message, it only passes a pointer to a message. When a task creates a message (which can have many Kilobytes of data attached to it) and sends it to another task, the actual message does not move.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Essentially, &#039;&#039;&#039;when &#039;&#039;task A&#039;&#039; sends a message to &#039;&#039;task B&#039;&#039;, &#039;&#039;task A&#039;&#039; lends &#039;&#039;task B&#039;&#039; a chunk of its memory, the memory occupied by the message. After &#039;&#039;task A&#039;&#039; sends the message, it has relinquished that memory to &#039;&#039;task B&#039;&#039;, so it cannot touch the memory occupied by the message. &#039;&#039;Task B&#039;&#039; has control of that memory until &#039;&#039;task B&#039;&#039; returns the message to &#039;&#039;task A&#039;&#039; with the ReplyMsg() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Let&#039;s look at an example. Many applications use Intuition windows as a source for user input. Without getting into too much detail about Intuition, when an application opens a window, it can set up the window so Intuition will send messages about certain user input events. Intuition sends these messages to a message port created by Intuition for use with this window. When an application successfully opens a window, it receives a pointer to a Window structure, which contains a pointer to this message port (Window.UserPort). For this example, we&#039;ll assume the window has been set up so Intuition will send a message only if the user clicks the window&#039;s close gadget.&lt;br /&gt;
&lt;br /&gt;
When Intuition opens the window in this example, it creates a message port for the task that opened the Window. Because the most common message passing system uses signals, creating this message port involves using one of the example task&#039;s 32 signals. The OS uses this signal to signal the task when it receives a message at this message port. This allows the task to sleep while waiting for a &amp;quot;Close Window&amp;quot; event to arrive. Since this simple example is only waiting for activity at one message port, it can use the WaitPort() function. WaitPort() accepts a pointer to a message port and puts a task to sleep until a message arrives at that port.&lt;br /&gt;
&lt;br /&gt;
This simple example needs two variables, one to hold the address of the window and the other to hold the address of a message.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Message *mymsg; /*defined in &amp;lt;exec/ports.h&amp;gt; */&lt;br /&gt;
struct Window *mywin;  /* defined in &amp;lt;intuition/intuition.h&amp;gt; */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* at some point, this application would have successfully opened a */&lt;br /&gt;
/* window and stored a pointer to it in mywin.                      */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    /* Here the application goes to sleep until the user clicks the window&#039;s close    */&lt;br /&gt;
    /* gadget.  This window was set up so that the only time Intuition will send a    */&lt;br /&gt;
    /* message to this window&#039;s port is if the user clicks the window&#039;s close gadget. */&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;WaitPort(mywin-&amp;gt;UserPort);&lt;br /&gt;
    while (mymsg = IExec-&amp;gt;GetMsg(mywin-&amp;gt;UserPort))&lt;br /&gt;
    {&lt;br /&gt;
        /* process message now or copy information from message */&lt;br /&gt;
        IExec-&amp;gt;ReplyMsg(mymsg);&lt;br /&gt;
    }&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* Close window, clean up */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Exec function GetMsg() is used to extract messages from the message port. Since the memory for these messages belongs to Intuition, the example relinquishes each message as it finishes with them by calling ReplyMsg(). Notice that the example keeps on trying to get messages from the message port until mymsg is NULL. This is to make sure there are no messages left in the message port&#039;s message queue. It is possible for several messages to pile up in the message queue while the task is asleep, so the example has to make sure it replies to all of them. Note that the window should never be closed within this GetMsg() loop because the while statement is still accessing the window&#039;s UserPort.&lt;br /&gt;
&lt;br /&gt;
Note that each task with a Process structure (sometimes referred to as a process) has a special process message port, Process.pr_MsgPort. This message port is only for use by Workbench and the DOS library itself. &#039;&#039;No application should use this port for its own use!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
More detailed information about message ports can be found in [[Exec_Messages_and_Ports|Exec Messages and Ports]].&lt;br /&gt;
&lt;br /&gt;
==== Waiting on Message Ports and Signals at the Same Time ====&lt;br /&gt;
&lt;br /&gt;
Most applications need to wait for a variety of messages and signals from a variety of sources. For example, an application might be waiting for Window events and also timer.device messages. In this case, an application must Wait() on the combined signal bits of all signals it is interested in, including the signals for the message ports where any messages might arrive.&lt;br /&gt;
&lt;br /&gt;
The MsgPort structure, which is defined in &amp;amp;lt;exec/ports.h&amp;amp;gt;, is what Exec uses to keep track of a message port. The UserPort field from the example above points to one of these structures. In this structure is a field called mp_SigBit, which contains the &#039;&#039;number&#039;&#039; (not the actual bit mask) of the message port&#039;s signal bit. To Wait() on the signal of a message port, Wait() on a bit mask created by shifting 1U to the left mp_SigBit times (1U &amp;lt;&amp;lt; msgport-&amp;amp;gt;mp_SigBit). The resulting bit mask can be &#039;&#039;&#039;OR&#039;&#039;&#039;&#039;d with the bit masks for any other signals you wish to simultaneously wait on.&lt;br /&gt;
&lt;br /&gt;
== Libraries and Devices ==&lt;br /&gt;
&lt;br /&gt;
One of the design goals for the Amiga OS was to make the system dynamic enough so that the OS could be extended and updated without effecting existing applications. Another design goal was to make it easy for different applications to be able to share common pieces of code. The Amiga accomplished these goals through a system of libraries. An Amiga library consists of a collection of related functions which can be anywhere in system memory (RAM or ROM).&lt;br /&gt;
&lt;br /&gt;
Devices are very similar to libraries, except they usually control some sort of I/O device and contain some extra standard functions. Although this section does not really discuss devices directly, the material here applies to them. For more information on devices, see the [[Exec Device I/O]] section of this manual or the [[Devices|Devices]].&lt;br /&gt;
&lt;br /&gt;
An application accesses a library&#039;s functions through interfaces. Each library exports one or more interfaces. Before a task can use the functions of a particular interface, it must first acquire the library&#039;s base pointer by calling the Exec OpenLibrary() function and then obtain an interface pointer by calling GetInterface().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Library *OpenLibrary( CONST_STRPTR libName, uint32 libVer );&lt;br /&gt;
struct Interface *GetInterface( struct Library *base, CONST_STRPTR ifaceName, uint32 ifaceVer, struct TagItem *tags);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where libName is a string naming the library and libVer is a version number for the library. The library base pointer is passed on to GetInterface() along with ifaceName which is the name of the interface and ifaceVer which is the version. An optional tag array completes the call.&lt;br /&gt;
&lt;br /&gt;
The libVer number reflects a revision of the system software. The chart below lists the specific Amiga OS release versions that system libraries versions correspond to:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 27.3 || Kickstart 0.7 || First AmigaOS version shown at Consumer Electronics Show in 1985. This version is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 29 || Kickstart 0.9 || This is an internal AmigaOS version. This version is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 30 || Kickstart 1.0 || This revision is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 31 || Kickstart 1.1 || This was an NTSC only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 32 || Kickstart 1.1 || This was a PAL only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 33 || Kickstart 1.2 || This is the oldest revision of the OS.&lt;br /&gt;
|-&lt;br /&gt;
| 34 || Kickstart 1.3 || This is almost the same as release 1.2 except it has an Autobooting expansion.library&lt;br /&gt;
|-&lt;br /&gt;
| 35 || || This is a special RAM-loaded version of the 1.3 revision, except that it knows about the &#039;&#039;A2024&#039;&#039; display modes. No application should need this library unless they need to open an &#039;&#039;A2024&#039;&#039; display mode under 1.3.&lt;br /&gt;
|-&lt;br /&gt;
| 36 || Kickstart 2.0 || This is the original Release 2 revision that was initially shipped on early &#039;&#039;Amiga 3000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 37 || Kickstart 2.04 || This is the general Release 2 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 38 || Kickstart 2.1 || This is an updated Release 2 revision for &#039;&#039;Amiga 600&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 39 || Kickstart 3.0 || This is the original Release 3 revision hat was initially shipped on &#039;&#039;Amiga 1200&#039;&#039; and &#039;&#039;Amiga 4000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 40 || Kickstart 3.1 || This is the original Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 41 || Kickstart 3.1 || This is version number which was reserved for the Japanese locale version of AmigaOS 3.1, with multi-byte character support (never completed).&lt;br /&gt;
|-&lt;br /&gt;
| 42 ||  || This is version number for some AmigaOS&#039; modules of an internal alpha revision (early 1993).&lt;br /&gt;
|-&lt;br /&gt;
| 43 || Kickstart 3.2 || This is the original Release 3 revision for Amiga Walker model (never completed). This revision was used for stuff done by/for Amiga Technologies/Amiga International (1995).&lt;br /&gt;
|-&lt;br /&gt;
| 44 || Kickstart 3.5 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 45 || Kickstart 3.9 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 50 || Kickstart 4.0 || This is the general Release 4 revision for the new &#039;&#039;AmigaOne&#039;&#039; range (Developer PreRelease).&lt;br /&gt;
|-&lt;br /&gt;
| 51 || Kickstart 4.0 || This is an update to Release 4 revision for the new &#039;&#039;AmigaOne&#039;&#039; range (Official Release from Update 1).&lt;br /&gt;
|-&lt;br /&gt;
| 52 || Kickstart 4.0 || This is an update to Release 4 revision to include support for PPC equipped classic Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 53 || Kickstart 4.1 || This is an update to Release 4 revision for all supported PPC models.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The OpenLibrary() function looks for a library with a name that matches libName and also with a version at least as high as libVersion. For example, to open version 50 or greater of the Intuition library:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example, if version 50 or greater of the Intuition library is not available, OpenLibrary() returns NULL. A version of zero in OpenLibrary() tells the OS to open any version of the library. New code should use a version of 50 or higher unless noted otherwise.&lt;br /&gt;
&lt;br /&gt;
When OpenLibrary() looks for a library, it first looks in memory. If the library is not in memory, OpenLibrary() will look for the library on disk. If libName is a library name with an absolute path (for example, &amp;quot;myapp:mylibs/mylib.library&amp;quot;), OpenLibrary() will follow that absolute path looking for the library. If libName is only a library name (&amp;quot;diskfont.library&amp;quot;), OpenLibrary() will look for the library in the directory that the LIBS: logical assign currently references.&lt;br /&gt;
&lt;br /&gt;
If OpenLibrary() finds the library on disk, it takes care of loading it and initializing it. As part of the initialization process, OpenLibrary() dynamically creates a vector table. There is a vector for each function in the library. The OS needs to create the vector table dynamically because the library functions can be anywhere in memory.&lt;br /&gt;
&lt;br /&gt;
After the library is loaded into memory and initialized, OpenLibrary() can actually &amp;quot;open&amp;quot; the library. It does this by calling the library&#039;s Open function vector. Every library has a standard vector set aside for an OPEN function so the library can set up any data or processes that it needs. Normally, a library&#039;s OPEN function increments its open count to keep track of how many tasks currently have the library opened.&lt;br /&gt;
&lt;br /&gt;
If any step of OpenLibrary() fails, it returns a NULL value. If OpenLibrary() is successful, it returns the address of the library base. The library base is the address of this library&#039;s Library structure (defined in &amp;quot;exec/libraries.h&amp;quot;). The Library structure immediately follows the vector table in memory.&lt;br /&gt;
&lt;br /&gt;
Once the library has been opened the application needs to obtain access to the interfaces with GetInterface() in order to call the functions available. Every task or process is required to call GetInterface() on the instance and should not share the interface pointer.&lt;br /&gt;
&lt;br /&gt;
After an application is finished with a library, it &#039;&#039;must&#039;&#039; close it by calling DropInterface() on each interface and finally CloseLibrary():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
VOID DropInterface(struct Interface *ifacePtr);&lt;br /&gt;
VOID CloseLibrary(struct Library *libPtr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where ifacePtr is a pointer to an interface and libPtr is a pointer to the library base returned when the library was opened with OpenLibrary().&lt;br /&gt;
&lt;br /&gt;
For more detailed information about libraries and interfaces see [[Exec_Libraries|Exec Libraries]].&lt;br /&gt;
&lt;br /&gt;
=== Calling a Library Function ===&lt;br /&gt;
&lt;br /&gt;
To call a function in an Amiga system library a pointer to an interface is required. For example to call the Intuition function DisplayBeep():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IntuitionIFace *IIntuition = IExec-&amp;gt;GetInterface(base, &amp;quot;main&amp;quot; 1, NULL);&lt;br /&gt;
&lt;br /&gt;
IIntuition-&amp;gt;DisplayBeep();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Function parameters in C are pushed on the stack when a program calls a function. The order in which parameters are pushed is defined by the [http://en.wikipedia.org/wiki/Executable_and_Linkable_Format SysV ABI specification].&lt;br /&gt;
&lt;br /&gt;
For more detailed information about calling library functions see [[Exec_Libraries|Exec Libraries]].&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3390</id>
		<title>Introduction to Exec</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3390"/>
		<updated>2012-07-08T10:04:12Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Interprocess Communications */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to Exec ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;Multitasking Executive&#039;&#039;, better known as &#039;&#039;Exec&#039;&#039;, is the heart of the Amiga&#039;s operating system. All other systems in the Amiga rely on it to control multitasking, to manage the message-based interprocess communications system, and to arbitrate access to system resources. Because just about every software entity on the Amiga (including application programs) needs to use Exec in some way, every Amiga programmer has to have a basic understanding of its fundamentals.&lt;br /&gt;
&lt;br /&gt;
== Multitasking ==&lt;br /&gt;
&lt;br /&gt;
A conventional micro-computer spends a lot of its time waiting for things to happen. It has to wait for such things as the user to push buttons on the keyboard or mouse, for data to come in through the serial port, and for data to go out to a disk drive. To make efficient use of the CPU&#039;s time, an operating system can have the CPU carry out some other task while it is waiting for such events to occur.&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;multitasking&#039;&#039; operating system reduces the amount of time it wastes, by switching to another program when the current one needs to wait for an event. A multitasking operating system can have several programs, or tasks, running at the same time. Each task runs independently of the others, without having to worry about what the other tasks are doing. From a task&#039;s point of view, it&#039;s as if each task has a computer all to itself.&lt;br /&gt;
&lt;br /&gt;
The Amiga&#039;s multitasking works by switching which task is currently using the CPU. A task can be a user&#039;s application program, or it can be a task which controls system resources (like the disk drives or the keyboard). Each task has a priority assigned to it. Exec will let the task with the highest priority use the CPU, but only if the task is ready to run. A task can be in one of three states: &#039;&#039;ready&#039;&#039;, &#039;&#039;sleeping&#039;&#039;, or &#039;&#039;running&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A ready task is not currently using the CPU but is waiting to use the processor. Exec keeps a list of the tasks that are ready. Exec sorts this list according to task priority, so Exec can easily find the ready task with the highest priority. When Exec switches the task that currently has control of the CPU, it switches to the task at the top of this list.&lt;br /&gt;
&lt;br /&gt;
A sleeping task is not currently running and is waiting for some event to happen. When that event occurs, Exec will move the sleeping task into the list of ready tasks.&lt;br /&gt;
&lt;br /&gt;
A running task is currently using the CPU. It will remain the current task until one of three things occur:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;A higher priority task becomes ready, so the OS preempts the current task and switches to the higher priority task.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task needs to wait for an event, so it goes to sleep and Exec switches to the highest priority task in Exec&#039;s ready list.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task has had control of the CPU for at least a preset time period called a &#039;&#039;quantum&#039;&#039; and there is another task of equal priority ready to run. In this case, Exec will preempt the current task for the ready one with the same priority. This is known as &#039;&#039;time-slicing&#039;&#039;. When there is a group of tasks of equal priority on the top of the ready list, Exec will cycle through them, letting each one use the CPU for a quantum (a slice of time).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The terms &amp;quot;task&amp;quot; and &amp;quot;process&amp;quot; are often used interchangeably to represent the generic concept of task. On the Amiga, this terminology can be a little confusing because of the names of the data structures that are associated with Exec tasks. Each task has a structure associated with it called a Task structure (defined in &amp;amp;lt;exec/tasks.h&amp;amp;gt;). Most application tasks use a superset of the Task structure called a Process structure (defined in &amp;amp;lt;dos/dosextens.h&amp;amp;gt;). These terms are confusing to Amiga programmers because there is an important distinction between the Exec task with only a Task structure and an Exec task with a Process structure.&lt;br /&gt;
&lt;br /&gt;
The Process structure builds on the Task structure and contains some extra fields which allow the DOS library to associate an AmigaDOS environment to the task. Some elements of a DOS environment include a current input and output stream and a current working directory. These elements are important to applications that need to do standard input and output using functions like printf().&lt;br /&gt;
&lt;br /&gt;
Exec only pays attention to the Task structure portion of the Process structure, so, as far as Exec is concerned, there is no difference between a task with a Task structure and a task with a Process structure. Exec considers both of them to be tasks.&lt;br /&gt;
&lt;br /&gt;
An application doesn&#039;t normally worry about which structure their task uses. Instead, the system that launches the application takes care of it. Both Workbench and the Shell (CLI) attach a Process structure to the application tasks that they launch.&lt;br /&gt;
&lt;br /&gt;
== Dynamic Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
The Amiga has a soft machine architecture, meaning that all tasks, including those that are part of its operating system, do not use fixed memory addresses. As a result, any program that needs to use a chunk of memory must allocate that memory from the operating system.&lt;br /&gt;
&lt;br /&gt;
There is one function on the Amiga for simple memory allocation: AllocVecTags(). This function accept the a uint32 containing the size of the memory block in bytes followed by a TagItem array for memory attributes. The function returns the address of a memory block aligned to your specifications if successful or NULL if something went wrong.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any tags when allocating memory, the system looks for MEMF_PRIVATE memory. There are additional memory allocation tags: MEMF_SHARED and MEMF_EXECUTABLE. See the [[Exec_Memory_Allocation|Exec Memory Allocation]] section for additional information on attributes and tags.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Make Sure You Have Memory.&#039;&#039; Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
FreeVec() releases memory allocated by AllocVecTags(). It takes only one parameter, a pointer to a memory block allocated by AllocVecTags(). The following example shows how to allocate and deallocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR my_mem = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (my_mem != NULL)&lt;br /&gt;
{&lt;br /&gt;
    /* Your code goes here */&lt;br /&gt;
    IExec-&amp;gt;FreeVec(my_mem);&lt;br /&gt;
}&lt;br /&gt;
else  { /* couldn&#039;t get memory, exit with an error */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Signals ==&lt;br /&gt;
&lt;br /&gt;
The Amiga uses a mechanism called &#039;&#039;signals&#039;&#039; to tell a task that some event occurred. Each task has its own set of 32 signals, 16 of which are set aside for system use. When one task signals a second task, it asks the OS to set a specific bit in the 32-bit long word set aside for the second task&#039;s signals.&lt;br /&gt;
&lt;br /&gt;
Signals are what makes it possible for a task to go to sleep. When a task goes to sleep, it asks the OS to wake it up when a specific signal bit gets set. That bit is tied to some event. When that event occurs, that signal bit gets set. This triggers the OS into waking up the sleeping task.&lt;br /&gt;
&lt;br /&gt;
To go to sleep, a task calls a system function called Wait(). This function takes one argument, a bitmask that tells Exec which of the task&#039;s signal bits to &amp;quot;listen to&amp;quot;. The task will only wake up if it receives one of the signals whose corresponding bit is set in that bitmask. For example, if a task wanted wait for signals 17 and 19, it would call Wait() like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
mysignals = IExec-&amp;gt;Wait(1U &amp;lt;&amp;lt; 17 | 1U &amp;lt;&amp;lt; 19);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wait() puts the task to sleep and will not return until some other task sets at least one of these two signals. When the task wakes up, mysignals will contain the bitmask of the signal or signals that woke up the task. It is possible for several signals to occur simultaneously, so any combination of the signals that the task Wait()ed on can occur. It is up to the waking task to use the return value from Wait() to figure out which signal or signals occurred.&lt;br /&gt;
&lt;br /&gt;
More information on signals can be found in [[Exec_Signals|Exec Signals]].&lt;br /&gt;
&lt;br /&gt;
== Interprocess Communications ==&lt;br /&gt;
&lt;br /&gt;
Another feature of the Amiga OS is its system of message-based &#039;&#039;interprocess communication&#039;&#039;. Using this system, a task can send a message to a message port owned by another task. Tasks use this mechanism to do things like trigger events or share data with other tasks, including system tasks. Exec&#039;s message system is built on top of Exec&#039;s task signaling mechanism. Most Amiga applications programming (especially Intuition programming) relies heavily upon this message-based form of interprocess communication.&lt;br /&gt;
&lt;br /&gt;
When one task sends a message to another task&#039;s message port, the OS adds the message to the port&#039;s message queue. The message stays in this queue until the task that owns the port is ready to check its port for messages. Typically, a task has put itself to sleep while it is waiting for an event, like a message to arrive at its message port. When the message arrives, the task wakes up to look in its message port. The messages in the message port&#039;s queue are arranged in first-in-first-out (FIFO) order so that, when a task receives several messages, it will see the messages in the order they arrived at the&lt;br /&gt;
&lt;br /&gt;
A task can use a message to share any kind of data with another task. &#039;&#039;&#039;This is possible because a task does not actually transmit an entire message, it only passes a pointer to a message. When a task creates a message (which can have many Kilobytes of data attached to it) and sends it to another task, the actual message does not move.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Essentially, &#039;&#039;&#039;when &#039;&#039;task A&#039;&#039; sends a message to &#039;&#039;task B&#039;&#039;, &#039;&#039;task A&#039;&#039; lends &#039;&#039;task B&#039;&#039; a chunk of its memory, the memory occupied by the message. After &#039;&#039;task A&#039;&#039; sends the message, it has relinquished that memory to &#039;&#039;task B&#039;&#039;, so it cannot touch the memory occupied by the message. &#039;&#039;Task B&#039;&#039; has control of that memory until &#039;&#039;task B&#039;&#039; returns the message to &#039;&#039;task A&#039;&#039; with the ReplyMsg() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Let&#039;s look at an example. Many applications use Intuition windows as a source for user input. Without getting into too much detail about Intuition, when an application opens a window, it can set up the window so Intuition will send messages about certain user input events. Intuition sends these messages to a message port created by Intuition for use with this window. When an application successfully opens a window, it receives a pointer to a Window structure, which contains a pointer to this message port (Window.UserPort). For this example, we&#039;ll assume the window has been set up so Intuition will send a message only if the user clicks the window&#039;s close gadget.&lt;br /&gt;
&lt;br /&gt;
When Intuition opens the window in this example, it creates a message port for the task that opened the Window. Because the most common message passing system uses signals, creating this message port involves using one of the example task&#039;s 32 signals. The OS uses this signal to signal the task when it receives a message at this message port. This allows the task to sleep while waiting for a &amp;quot;Close Window&amp;quot; event to arrive. Since this simple example is only waiting for activity at one message port, it can use the WaitPort() function. WaitPort() accepts a pointer to a message port and puts a task to sleep until a message arrives at that port.&lt;br /&gt;
&lt;br /&gt;
This simple example needs two variables, one to hold the address of the window and the other to hold the address of a message.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Message *mymsg; /*defined in &amp;lt;exec/ports.h&amp;gt; */&lt;br /&gt;
struct Window *mywin;  /* defined in &amp;lt;intuition/intuition.h&amp;gt; */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* at some point, this application would have successfully opened a */&lt;br /&gt;
/* window and stored a pointer to it in mywin.                      */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    /* Here the application goes to sleep until the user clicks the window&#039;s close    */&lt;br /&gt;
    /* gadget.  This window was set up so that the only time Intuition will send a    */&lt;br /&gt;
    /* message to this window&#039;s port is if the user clicks the window&#039;s close gadget. */&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;WaitPort(mywin-&amp;gt;UserPort);&lt;br /&gt;
    while (mymsg = IExec-&amp;gt;GetMsg(mywin-&amp;gt;UserPort))&lt;br /&gt;
    {&lt;br /&gt;
        /* process message now or copy information from message */&lt;br /&gt;
        IExec-&amp;gt;ReplyMsg(mymsg);&lt;br /&gt;
    }&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* Close window, clean up */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Exec function GetMsg() is used to extract messages from the message port. Since the memory for these messages belongs to Intuition, the example relinquishes each message as it finishes with them by calling ReplyMsg(). Notice that the example keeps on trying to get messages from the message port until mymsg is NULL. This is to make sure there are no messages left in the message port&#039;s message queue. It is possible for several messages to pile up in the message queue while the task is asleep, so the example has to make sure it replies to all of them. Note that the window should never be closed within this GetMsg() loop because the while statement is still accessing the window&#039;s UserPort.&lt;br /&gt;
&lt;br /&gt;
Note that each task with a Process structure (sometimes referred to as a process) has a special process message port, Process.pr_MsgPort. This message port is only for use by Workbench and the DOS library itself. &#039;&#039;No application should use this port for its own use!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
More detailed information about message ports can be found in [[Exec_Messages_and_Ports|Exec Messages and Ports]].&lt;br /&gt;
&lt;br /&gt;
==== Waiting on Message Ports and Signals at the Same Time ====&lt;br /&gt;
&lt;br /&gt;
Most applications need to wait for a variety of messages and signals from a variety of sources. For example, an application might be waiting for Window events and also timer.device messages. In this case, an application must Wait() on the combined signal bits of all signals it is interested in, including the signals for the message ports where any messages might arrive.&lt;br /&gt;
&lt;br /&gt;
The MsgPort structure, which is defined in &amp;amp;lt;exec/ports.h&amp;amp;gt;, is what Exec uses to keep track of a message port. The UserPort field from the example above points to one of these structures. In this structure is a field called mp_SigBit, which contains the &#039;&#039;number&#039;&#039; (not the actual bit mask) of the message port&#039;s signal bit. To Wait() on the signal of a message port, Wait() on a bit mask created by shifting 1U to the left mp_SigBit times (1U &amp;lt;&amp;lt; msgport-&amp;amp;gt;mp_SigBit). The resulting bit mask can be &#039;&#039;&#039;OR&#039;&#039;&#039;&#039;d with the bit masks for any other signals you wish to simultaneously wait on.&lt;br /&gt;
&lt;br /&gt;
== Libraries and Devices ==&lt;br /&gt;
&lt;br /&gt;
One of the design goals for the Amiga OS was to make the system dynamic enough so that the OS could be extended and updated without effecting existing applications. Another design goal was to make it easy for different applications to be able to share common pieces of code. The Amiga accomplished these goals through a system of libraries. An Amiga library consists of a collection of related functions which can be anywhere in system memory (RAM or ROM).&lt;br /&gt;
&lt;br /&gt;
Devices are very similar to libraries, except they usually control some sort of I/O device and contain some extra standard functions. Although this section does not really discuss devices directly, the material here applies to them. For more information on devices, see the [[Exec Device I/O]] section of this manual or the [[Devices|Devices]].&lt;br /&gt;
&lt;br /&gt;
An application accesses a library&#039;s functions through interfaces. Each library exports one or more interfaces. Before a task can use the functions of a particular interface, it must first acquire the library&#039;s base pointer by calling the Exec OpenLibrary() function and then obtain an interface pointer by calling GetInterface().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Library *OpenLibrary( CONST_STRPTR libName, uint32 libVer );&lt;br /&gt;
struct Interface *GetInterface( struct Library *base, CONST_STRPTR ifaceName, uint32 ifaceVer, struct TagItem *tags);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where libName is a string naming the library and libVer is a version number for the library. The library base pointer is passed on to GetInterface() along with ifaceName which is the name of the interface and ifaceVer which is the version. An optional tag array completes the call.&lt;br /&gt;
&lt;br /&gt;
The libVer number reflects a revision of the system software. The chart below lists the specific Amiga OS release versions that system libraries versions correspond to:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 30 || Kickstart 1.0 || This revision is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 31 || Kickstart 1.1 || This was an NTSC only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 32 || Kickstart 1.1 || This was a PAL only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 33 || Kickstart 1.2 ||This is the oldest revision of the OS still in use.&lt;br /&gt;
|-&lt;br /&gt;
| 34 || Kickstart 1.3 || This is almost the same as release 1.2 except it has an Autobooting expansion.library&lt;br /&gt;
|-&lt;br /&gt;
| 35 || || This is a special RAM-loaded version of the 1.3 revision, except that it knows about the &#039;&#039;A2024&#039;&#039; display modes. No application should need this library unless they need to open an &#039;&#039;A2024&#039;&#039; display mode under 1.3.&lt;br /&gt;
|-&lt;br /&gt;
| 36 || Kickstart 2.0 || This is the original Release 2 revision that was initially shipped on early &#039;&#039;Amiga 3000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 37 || Kickstart 2.04 || This is the general Release 2 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 38 || Kickstart 2.1 || This is an updated Release 2 revision for &#039;&#039;Amiga 600&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 39 || Kickstart 3.0 || This is the original Release 3 revision hat was initially shipped on &#039;&#039;Amiga 1200&#039;&#039; and &#039;&#039;Amiga 4000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 40 || Kickstart 3.1 || This is the general Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 44 || Kickstart 3.5 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 45 || Kickstart 3.9 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 50 || Kickstart 4.0 || This is the general Release 4 revision for PPC equipped Amiga models and the new &#039;&#039;AmigaOne&#039;&#039; range.&lt;br /&gt;
|-&lt;br /&gt;
| 51 || || &lt;br /&gt;
|-&lt;br /&gt;
| 52 || Kickstart 4.0 || This is an update to Release 4 revision to include support for PPC equipped classic Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 53 || Kickstart 4.1 || This is an update to Release 4 revision for all supported models.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The OpenLibrary() function looks for a library with a name that matches libName and also with a version at least as high as libVersion. For example, to open version 50 or greater of the Intuition library:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example, if version 50 or greater of the Intuition library is not available, OpenLibrary() returns NULL. A version of zero in OpenLibrary() tells the OS to open any version of the library. New code should use a version of 50 or higher unless noted otherwise.&lt;br /&gt;
&lt;br /&gt;
When OpenLibrary() looks for a library, it first looks in memory. If the library is not in memory, OpenLibrary() will look for the library on disk. If libName is a library name with an absolute path (for example, &amp;quot;myapp:mylibs/mylib.library&amp;quot;), OpenLibrary() will follow that absolute path looking for the library. If libName is only a library name (&amp;quot;diskfont.library&amp;quot;), OpenLibrary() will look for the library in the directory that the LIBS: logical assign currently references.&lt;br /&gt;
&lt;br /&gt;
If OpenLibrary() finds the library on disk, it takes care of loading it and initializing it. As part of the initialization process, OpenLibrary() dynamically creates a vector table. There is a vector for each function in the library. The OS needs to create the vector table dynamically because the library functions can be anywhere in memory.&lt;br /&gt;
&lt;br /&gt;
After the library is loaded into memory and initialized, OpenLibrary() can actually &amp;quot;open&amp;quot; the library. It does this by calling the library&#039;s Open function vector. Every library has a standard vector set aside for an OPEN function so the library can set up any data or processes that it needs. Normally, a library&#039;s OPEN function increments its open count to keep track of how many tasks currently have the library opened.&lt;br /&gt;
&lt;br /&gt;
If any step of OpenLibrary() fails, it returns a NULL value. If OpenLibrary() is successful, it returns the address of the library base. The library base is the address of this library&#039;s Library structure (defined in &amp;quot;exec/libraries.h&amp;quot;). The Library structure immediately follows the vector table in memory.&lt;br /&gt;
&lt;br /&gt;
Once the library has been opened the application needs to obtain access to the interfaces with GetInterface() in order to call the functions available. Every task or process is required to call GetInterface() on the instance and should not share the interface pointer.&lt;br /&gt;
&lt;br /&gt;
After an application is finished with a library, it &#039;&#039;must&#039;&#039; close it by calling DropInterface() on each interface and finally CloseLibrary():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
VOID DropInterface(struct Interface *ifacePtr);&lt;br /&gt;
VOID CloseLibrary(struct Library *libPtr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where ifacePtr is a pointer to an interface and libPtr is a pointer to the library base returned when the library was opened with OpenLibrary().&lt;br /&gt;
&lt;br /&gt;
For more detailed information about libraries and interfaces see [[Exec_Libraries|Exec Libraries]].&lt;br /&gt;
&lt;br /&gt;
=== Calling a Library Function ===&lt;br /&gt;
&lt;br /&gt;
To call a function in an Amiga system library a pointer to an interface is required. For example to call the Intuition function DisplayBeep():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IntuitionIFace *IIntuition = IExec-&amp;gt;GetInterface(base, &amp;quot;main&amp;quot; 1, NULL);&lt;br /&gt;
&lt;br /&gt;
IIntuition-&amp;gt;DisplayBeep();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Function parameters in C are pushed on the stack when a program calls a function. The order in which parameters are pushed is defined by the [http://en.wikipedia.org/wiki/Executable_and_Linkable_Format SysV ABI specification].&lt;br /&gt;
&lt;br /&gt;
For more detailed information about calling library functions see [[Exec_Libraries|Exec Libraries]].&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3389</id>
		<title>Introduction to Exec</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3389"/>
		<updated>2012-07-08T10:00:50Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Interprocess Communications */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to Exec ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;Multitasking Executive&#039;&#039;, better known as &#039;&#039;Exec&#039;&#039;, is the heart of the Amiga&#039;s operating system. All other systems in the Amiga rely on it to control multitasking, to manage the message-based interprocess communications system, and to arbitrate access to system resources. Because just about every software entity on the Amiga (including application programs) needs to use Exec in some way, every Amiga programmer has to have a basic understanding of its fundamentals.&lt;br /&gt;
&lt;br /&gt;
== Multitasking ==&lt;br /&gt;
&lt;br /&gt;
A conventional micro-computer spends a lot of its time waiting for things to happen. It has to wait for such things as the user to push buttons on the keyboard or mouse, for data to come in through the serial port, and for data to go out to a disk drive. To make efficient use of the CPU&#039;s time, an operating system can have the CPU carry out some other task while it is waiting for such events to occur.&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;multitasking&#039;&#039; operating system reduces the amount of time it wastes, by switching to another program when the current one needs to wait for an event. A multitasking operating system can have several programs, or tasks, running at the same time. Each task runs independently of the others, without having to worry about what the other tasks are doing. From a task&#039;s point of view, it&#039;s as if each task has a computer all to itself.&lt;br /&gt;
&lt;br /&gt;
The Amiga&#039;s multitasking works by switching which task is currently using the CPU. A task can be a user&#039;s application program, or it can be a task which controls system resources (like the disk drives or the keyboard). Each task has a priority assigned to it. Exec will let the task with the highest priority use the CPU, but only if the task is ready to run. A task can be in one of three states: &#039;&#039;ready&#039;&#039;, &#039;&#039;sleeping&#039;&#039;, or &#039;&#039;running&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A ready task is not currently using the CPU but is waiting to use the processor. Exec keeps a list of the tasks that are ready. Exec sorts this list according to task priority, so Exec can easily find the ready task with the highest priority. When Exec switches the task that currently has control of the CPU, it switches to the task at the top of this list.&lt;br /&gt;
&lt;br /&gt;
A sleeping task is not currently running and is waiting for some event to happen. When that event occurs, Exec will move the sleeping task into the list of ready tasks.&lt;br /&gt;
&lt;br /&gt;
A running task is currently using the CPU. It will remain the current task until one of three things occur:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;A higher priority task becomes ready, so the OS preempts the current task and switches to the higher priority task.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task needs to wait for an event, so it goes to sleep and Exec switches to the highest priority task in Exec&#039;s ready list.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task has had control of the CPU for at least a preset time period called a &#039;&#039;quantum&#039;&#039; and there is another task of equal priority ready to run. In this case, Exec will preempt the current task for the ready one with the same priority. This is known as &#039;&#039;time-slicing&#039;&#039;. When there is a group of tasks of equal priority on the top of the ready list, Exec will cycle through them, letting each one use the CPU for a quantum (a slice of time).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The terms &amp;quot;task&amp;quot; and &amp;quot;process&amp;quot; are often used interchangeably to represent the generic concept of task. On the Amiga, this terminology can be a little confusing because of the names of the data structures that are associated with Exec tasks. Each task has a structure associated with it called a Task structure (defined in &amp;amp;lt;exec/tasks.h&amp;amp;gt;). Most application tasks use a superset of the Task structure called a Process structure (defined in &amp;amp;lt;dos/dosextens.h&amp;amp;gt;). These terms are confusing to Amiga programmers because there is an important distinction between the Exec task with only a Task structure and an Exec task with a Process structure.&lt;br /&gt;
&lt;br /&gt;
The Process structure builds on the Task structure and contains some extra fields which allow the DOS library to associate an AmigaDOS environment to the task. Some elements of a DOS environment include a current input and output stream and a current working directory. These elements are important to applications that need to do standard input and output using functions like printf().&lt;br /&gt;
&lt;br /&gt;
Exec only pays attention to the Task structure portion of the Process structure, so, as far as Exec is concerned, there is no difference between a task with a Task structure and a task with a Process structure. Exec considers both of them to be tasks.&lt;br /&gt;
&lt;br /&gt;
An application doesn&#039;t normally worry about which structure their task uses. Instead, the system that launches the application takes care of it. Both Workbench and the Shell (CLI) attach a Process structure to the application tasks that they launch.&lt;br /&gt;
&lt;br /&gt;
== Dynamic Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
The Amiga has a soft machine architecture, meaning that all tasks, including those that are part of its operating system, do not use fixed memory addresses. As a result, any program that needs to use a chunk of memory must allocate that memory from the operating system.&lt;br /&gt;
&lt;br /&gt;
There is one function on the Amiga for simple memory allocation: AllocVecTags(). This function accept the a uint32 containing the size of the memory block in bytes followed by a TagItem array for memory attributes. The function returns the address of a memory block aligned to your specifications if successful or NULL if something went wrong.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any tags when allocating memory, the system looks for MEMF_PRIVATE memory. There are additional memory allocation tags: MEMF_SHARED and MEMF_EXECUTABLE. See the [[Exec_Memory_Allocation|Exec Memory Allocation]] section for additional information on attributes and tags.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Make Sure You Have Memory.&#039;&#039; Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
FreeVec() releases memory allocated by AllocVecTags(). It takes only one parameter, a pointer to a memory block allocated by AllocVecTags(). The following example shows how to allocate and deallocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR my_mem = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (my_mem != NULL)&lt;br /&gt;
{&lt;br /&gt;
    /* Your code goes here */&lt;br /&gt;
    IExec-&amp;gt;FreeVec(my_mem);&lt;br /&gt;
}&lt;br /&gt;
else  { /* couldn&#039;t get memory, exit with an error */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Signals ==&lt;br /&gt;
&lt;br /&gt;
The Amiga uses a mechanism called &#039;&#039;signals&#039;&#039; to tell a task that some event occurred. Each task has its own set of 32 signals, 16 of which are set aside for system use. When one task signals a second task, it asks the OS to set a specific bit in the 32-bit long word set aside for the second task&#039;s signals.&lt;br /&gt;
&lt;br /&gt;
Signals are what makes it possible for a task to go to sleep. When a task goes to sleep, it asks the OS to wake it up when a specific signal bit gets set. That bit is tied to some event. When that event occurs, that signal bit gets set. This triggers the OS into waking up the sleeping task.&lt;br /&gt;
&lt;br /&gt;
To go to sleep, a task calls a system function called Wait(). This function takes one argument, a bitmask that tells Exec which of the task&#039;s signal bits to &amp;quot;listen to&amp;quot;. The task will only wake up if it receives one of the signals whose corresponding bit is set in that bitmask. For example, if a task wanted wait for signals 17 and 19, it would call Wait() like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
mysignals = IExec-&amp;gt;Wait(1U &amp;lt;&amp;lt; 17 | 1U &amp;lt;&amp;lt; 19);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wait() puts the task to sleep and will not return until some other task sets at least one of these two signals. When the task wakes up, mysignals will contain the bitmask of the signal or signals that woke up the task. It is possible for several signals to occur simultaneously, so any combination of the signals that the task Wait()ed on can occur. It is up to the waking task to use the return value from Wait() to figure out which signal or signals occurred.&lt;br /&gt;
&lt;br /&gt;
More information on signals can be found in [[Exec_Signals|Exec Signals]].&lt;br /&gt;
&lt;br /&gt;
== Interprocess Communications ==&lt;br /&gt;
&lt;br /&gt;
Another feature of the Amiga OS is its system of message-based &#039;&#039;interprocess communication&#039;&#039;. Using this system, a task can send a message to a message port owned by another task. Tasks use this mechanism to do things like trigger events or share data with other tasks, including system tasks. Exec&#039;s message system is built on top of Exec&#039;s task signaling mechanism. Most Amiga applications programming (especially Intuition programming) relies heavily upon this message-based form of interprocess communication.&lt;br /&gt;
&lt;br /&gt;
When one task sends a message to another task&#039;s message port, the OS adds the message to the port&#039;s message queue. The message stays in this queue until the task that owns the port is ready to check its port for messages. Typically, a task has put itself to sleep while it is waiting for an event, like a message to arrive at its message port. When the message arrives, the task wakes up to look in its message port. The messages in the message port&#039;s queue are arranged in first-in-first-out (FIFO) order so that, when a task receives several messages, it will see the messages in the order they arrived at the&lt;br /&gt;
&lt;br /&gt;
A task can use a message to share any kind of data with another task. &#039;&#039;&#039;This is possible because a task does not actually transmit an entire message, it only passes a pointer to a message. When a task creates a message (which can have many Kilobytes of data attached to it) and sends it to another task, the actual message does not move.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Essentially, &#039;&#039;&#039;when &#039;&#039;task A&#039;&#039; sends a message to &#039;&#039;task B&#039;&#039;, &#039;&#039;task A&#039;&#039; lends &#039;&#039;task B&#039;&#039; a chunk of its memory, the memory occupied by the message. After &#039;&#039;task A&#039;&#039; sends the message, it has relinquished that memory to &#039;&#039;task B&#039;&#039;, so it cannot touch the memory occupied by the message. &#039;&#039;Task B&#039;&#039; has control of that memory until &#039;&#039;task B&#039;&#039; returns the message to &#039;&#039;task A&#039;&#039; with the ReplyMsg() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Let&#039;s look at an example. Many applications use Intuition windows as a source for user input. Without getting into too much detail about Intuition, when an application opens a window, it can set up the window so Intuition will send messages about certain user input events. Intuition sends these messages to a message port created by Intuition for use with this window. When an application successfully opens a window, it receives a pointer to a Window structure, which contains a pointer to this message port (Window.UserPort). For this example, we&#039;ll assume the window has been set up so Intuition will send a message only if the user clicks the window&#039;s close gadget.&lt;br /&gt;
&lt;br /&gt;
When Intuition opens the window in this example, it creates a message port for the task that opened the Window. Because the most common message passing system uses signals, creating this message port involves using one of the example task&#039;s 32 signals. The OS uses this signal to signal the task when it receives a message at this message port. This allows the task to sleep while waiting for a &amp;quot;Close Window&amp;quot; event to arrive. Since this simple example is only waiting for activity at one message port, it can use the WaitPort() function. WaitPort() accepts a pointer to a message port and puts a task to sleep until a message arrives at that port.&lt;br /&gt;
&lt;br /&gt;
This simple example needs two variables, one to hold the address of the window and the other to hold the address of a message.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Message *mymsg; /*defined in &amp;lt;exec/ports.h&amp;gt; */&lt;br /&gt;
struct Window *mywin;  /* defined in &amp;lt;intuition/intuition.h&amp;gt; */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* at some point, this application would have successfully opened a */&lt;br /&gt;
/* window and stored a pointer to it in mywin.                      */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    /* Here the application goes to sleep until the user clicks the window&#039;s close    */&lt;br /&gt;
    /* gadget.  This window was set up so that the only time Intuition will send a    */&lt;br /&gt;
    /* message to this window&#039;s port is if the user clicks the window&#039;s close gadget. */&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;WaitPort(mywin-&amp;gt;UserPort);&lt;br /&gt;
    while (mymsg = IExec-&amp;gt;GetMsg(mywin-&amp;gt;UserPort))&lt;br /&gt;
        /* process message now or copy information from message */&lt;br /&gt;
        IExec-&amp;gt;ReplyMsg(mymsg);&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* Close window, clean up */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Exec function GetMsg() is used to extract messages from the message port. Since the memory for these messages belongs to Intuition, the example relinquishes each message as it finishes with them by calling ReplyMsg(). Notice that the example keeps on trying to get messages from the message port until mymsg is NULL. This is to make sure there are no messages left in the message port&#039;s message queue. It is possible for several messages to pile up in the message queue while the task is asleep, so the example has to make sure it replies to all of them. Note that the window should never be closed within this GetMsg() loop because the while statement is still accessing the window&#039;s UserPort.&lt;br /&gt;
&lt;br /&gt;
Note that each task with a Process structure (sometimes referred to as a process) has a special process message port, Process.pr_MsgPort. This message port is only for use by Workbench and the DOS library itself. &#039;&#039;No application should use this port for its own use!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
More detailed information about message ports can be found in [[Exec_Messages_and_Ports|Exec Messages and Ports]].&lt;br /&gt;
&lt;br /&gt;
==== Waiting on Message Ports and Signals at the Same Time ====&lt;br /&gt;
&lt;br /&gt;
Most applications need to wait for a variety of messages and signals from a variety of sources. For example, an application might be waiting for Window events and also timer.device messages. In this case, an application must Wait() on the combined signal bits of all signals it is interested in, including the signals for the message ports where any messages might arrive.&lt;br /&gt;
&lt;br /&gt;
The MsgPort structure, which is defined in &amp;amp;lt;exec/ports.h&amp;amp;gt;, is what Exec uses to keep track of a message port. The UserPort field from the example above points to one of these structures. In this structure is a field called mp_SigBit, which contains the &#039;&#039;number&#039;&#039; (not the actual bit mask) of the message port&#039;s signal bit. To Wait() on the signal of a message port, Wait() on a bit mask created by shifting 1U to the left mp_SigBit times (1U &amp;lt;&amp;lt; msgport-&amp;amp;gt;mp_SigBit). The resulting bit mask can be &#039;&#039;&#039;OR&#039;&#039;&#039;&#039;d with the bit masks for any other signals you wish to simultaneously wait on.&lt;br /&gt;
&lt;br /&gt;
== Libraries and Devices ==&lt;br /&gt;
&lt;br /&gt;
One of the design goals for the Amiga OS was to make the system dynamic enough so that the OS could be extended and updated without effecting existing applications. Another design goal was to make it easy for different applications to be able to share common pieces of code. The Amiga accomplished these goals through a system of libraries. An Amiga library consists of a collection of related functions which can be anywhere in system memory (RAM or ROM).&lt;br /&gt;
&lt;br /&gt;
Devices are very similar to libraries, except they usually control some sort of I/O device and contain some extra standard functions. Although this section does not really discuss devices directly, the material here applies to them. For more information on devices, see the [[Exec Device I/O]] section of this manual or the [[Devices|Devices]].&lt;br /&gt;
&lt;br /&gt;
An application accesses a library&#039;s functions through interfaces. Each library exports one or more interfaces. Before a task can use the functions of a particular interface, it must first acquire the library&#039;s base pointer by calling the Exec OpenLibrary() function and then obtain an interface pointer by calling GetInterface().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Library *OpenLibrary( CONST_STRPTR libName, uint32 libVer );&lt;br /&gt;
struct Interface *GetInterface( struct Library *base, CONST_STRPTR ifaceName, uint32 ifaceVer, struct TagItem *tags);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where libName is a string naming the library and libVer is a version number for the library. The library base pointer is passed on to GetInterface() along with ifaceName which is the name of the interface and ifaceVer which is the version. An optional tag array completes the call.&lt;br /&gt;
&lt;br /&gt;
The libVer number reflects a revision of the system software. The chart below lists the specific Amiga OS release versions that system libraries versions correspond to:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 30 || Kickstart 1.0 || This revision is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 31 || Kickstart 1.1 || This was an NTSC only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 32 || Kickstart 1.1 || This was a PAL only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 33 || Kickstart 1.2 ||This is the oldest revision of the OS still in use.&lt;br /&gt;
|-&lt;br /&gt;
| 34 || Kickstart 1.3 || This is almost the same as release 1.2 except it has an Autobooting expansion.library&lt;br /&gt;
|-&lt;br /&gt;
| 35 || || This is a special RAM-loaded version of the 1.3 revision, except that it knows about the &#039;&#039;A2024&#039;&#039; display modes. No application should need this library unless they need to open an &#039;&#039;A2024&#039;&#039; display mode under 1.3.&lt;br /&gt;
|-&lt;br /&gt;
| 36 || Kickstart 2.0 || This is the original Release 2 revision that was initially shipped on early &#039;&#039;Amiga 3000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 37 || Kickstart 2.04 || This is the general Release 2 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 38 || Kickstart 2.1 || This is an updated Release 2 revision for &#039;&#039;Amiga 600&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 39 || Kickstart 3.0 || This is the original Release 3 revision hat was initially shipped on &#039;&#039;Amiga 1200&#039;&#039; and &#039;&#039;Amiga 4000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 40 || Kickstart 3.1 || This is the general Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 44 || Kickstart 3.5 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 45 || Kickstart 3.9 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 50 || Kickstart 4.0 || This is the general Release 4 revision for PPC equipped Amiga models and the new &#039;&#039;AmigaOne&#039;&#039; range.&lt;br /&gt;
|-&lt;br /&gt;
| 51 || || &lt;br /&gt;
|-&lt;br /&gt;
| 52 || Kickstart 4.0 || This is an update to Release 4 revision to include support for PPC equipped classic Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 53 || Kickstart 4.1 || This is an update to Release 4 revision for all supported models.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The OpenLibrary() function looks for a library with a name that matches libName and also with a version at least as high as libVersion. For example, to open version 50 or greater of the Intuition library:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example, if version 50 or greater of the Intuition library is not available, OpenLibrary() returns NULL. A version of zero in OpenLibrary() tells the OS to open any version of the library. New code should use a version of 50 or higher unless noted otherwise.&lt;br /&gt;
&lt;br /&gt;
When OpenLibrary() looks for a library, it first looks in memory. If the library is not in memory, OpenLibrary() will look for the library on disk. If libName is a library name with an absolute path (for example, &amp;quot;myapp:mylibs/mylib.library&amp;quot;), OpenLibrary() will follow that absolute path looking for the library. If libName is only a library name (&amp;quot;diskfont.library&amp;quot;), OpenLibrary() will look for the library in the directory that the LIBS: logical assign currently references.&lt;br /&gt;
&lt;br /&gt;
If OpenLibrary() finds the library on disk, it takes care of loading it and initializing it. As part of the initialization process, OpenLibrary() dynamically creates a vector table. There is a vector for each function in the library. The OS needs to create the vector table dynamically because the library functions can be anywhere in memory.&lt;br /&gt;
&lt;br /&gt;
After the library is loaded into memory and initialized, OpenLibrary() can actually &amp;quot;open&amp;quot; the library. It does this by calling the library&#039;s Open function vector. Every library has a standard vector set aside for an OPEN function so the library can set up any data or processes that it needs. Normally, a library&#039;s OPEN function increments its open count to keep track of how many tasks currently have the library opened.&lt;br /&gt;
&lt;br /&gt;
If any step of OpenLibrary() fails, it returns a NULL value. If OpenLibrary() is successful, it returns the address of the library base. The library base is the address of this library&#039;s Library structure (defined in &amp;quot;exec/libraries.h&amp;quot;). The Library structure immediately follows the vector table in memory.&lt;br /&gt;
&lt;br /&gt;
Once the library has been opened the application needs to obtain access to the interfaces with GetInterface() in order to call the functions available. Every task or process is required to call GetInterface() on the instance and should not share the interface pointer.&lt;br /&gt;
&lt;br /&gt;
After an application is finished with a library, it &#039;&#039;must&#039;&#039; close it by calling DropInterface() on each interface and finally CloseLibrary():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
VOID DropInterface(struct Interface *ifacePtr);&lt;br /&gt;
VOID CloseLibrary(struct Library *libPtr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where ifacePtr is a pointer to an interface and libPtr is a pointer to the library base returned when the library was opened with OpenLibrary().&lt;br /&gt;
&lt;br /&gt;
For more detailed information about libraries and interfaces see [[Exec_Libraries|Exec Libraries]].&lt;br /&gt;
&lt;br /&gt;
=== Calling a Library Function ===&lt;br /&gt;
&lt;br /&gt;
To call a function in an Amiga system library a pointer to an interface is required. For example to call the Intuition function DisplayBeep():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IntuitionIFace *IIntuition = IExec-&amp;gt;GetInterface(base, &amp;quot;main&amp;quot; 1, NULL);&lt;br /&gt;
&lt;br /&gt;
IIntuition-&amp;gt;DisplayBeep();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Function parameters in C are pushed on the stack when a program calls a function. The order in which parameters are pushed is defined by the [http://en.wikipedia.org/wiki/Executable_and_Linkable_Format SysV ABI specification].&lt;br /&gt;
&lt;br /&gt;
For more detailed information about calling library functions see [[Exec_Libraries|Exec Libraries]].&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3388</id>
		<title>Introduction to Exec</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3388"/>
		<updated>2012-07-08T09:31:46Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Signals */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to Exec ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;Multitasking Executive&#039;&#039;, better known as &#039;&#039;Exec&#039;&#039;, is the heart of the Amiga&#039;s operating system. All other systems in the Amiga rely on it to control multitasking, to manage the message-based interprocess communications system, and to arbitrate access to system resources. Because just about every software entity on the Amiga (including application programs) needs to use Exec in some way, every Amiga programmer has to have a basic understanding of its fundamentals.&lt;br /&gt;
&lt;br /&gt;
== Multitasking ==&lt;br /&gt;
&lt;br /&gt;
A conventional micro-computer spends a lot of its time waiting for things to happen. It has to wait for such things as the user to push buttons on the keyboard or mouse, for data to come in through the serial port, and for data to go out to a disk drive. To make efficient use of the CPU&#039;s time, an operating system can have the CPU carry out some other task while it is waiting for such events to occur.&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;multitasking&#039;&#039; operating system reduces the amount of time it wastes, by switching to another program when the current one needs to wait for an event. A multitasking operating system can have several programs, or tasks, running at the same time. Each task runs independently of the others, without having to worry about what the other tasks are doing. From a task&#039;s point of view, it&#039;s as if each task has a computer all to itself.&lt;br /&gt;
&lt;br /&gt;
The Amiga&#039;s multitasking works by switching which task is currently using the CPU. A task can be a user&#039;s application program, or it can be a task which controls system resources (like the disk drives or the keyboard). Each task has a priority assigned to it. Exec will let the task with the highest priority use the CPU, but only if the task is ready to run. A task can be in one of three states: &#039;&#039;ready&#039;&#039;, &#039;&#039;sleeping&#039;&#039;, or &#039;&#039;running&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A ready task is not currently using the CPU but is waiting to use the processor. Exec keeps a list of the tasks that are ready. Exec sorts this list according to task priority, so Exec can easily find the ready task with the highest priority. When Exec switches the task that currently has control of the CPU, it switches to the task at the top of this list.&lt;br /&gt;
&lt;br /&gt;
A sleeping task is not currently running and is waiting for some event to happen. When that event occurs, Exec will move the sleeping task into the list of ready tasks.&lt;br /&gt;
&lt;br /&gt;
A running task is currently using the CPU. It will remain the current task until one of three things occur:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;A higher priority task becomes ready, so the OS preempts the current task and switches to the higher priority task.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task needs to wait for an event, so it goes to sleep and Exec switches to the highest priority task in Exec&#039;s ready list.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task has had control of the CPU for at least a preset time period called a &#039;&#039;quantum&#039;&#039; and there is another task of equal priority ready to run. In this case, Exec will preempt the current task for the ready one with the same priority. This is known as &#039;&#039;time-slicing&#039;&#039;. When there is a group of tasks of equal priority on the top of the ready list, Exec will cycle through them, letting each one use the CPU for a quantum (a slice of time).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The terms &amp;quot;task&amp;quot; and &amp;quot;process&amp;quot; are often used interchangeably to represent the generic concept of task. On the Amiga, this terminology can be a little confusing because of the names of the data structures that are associated with Exec tasks. Each task has a structure associated with it called a Task structure (defined in &amp;amp;lt;exec/tasks.h&amp;amp;gt;). Most application tasks use a superset of the Task structure called a Process structure (defined in &amp;amp;lt;dos/dosextens.h&amp;amp;gt;). These terms are confusing to Amiga programmers because there is an important distinction between the Exec task with only a Task structure and an Exec task with a Process structure.&lt;br /&gt;
&lt;br /&gt;
The Process structure builds on the Task structure and contains some extra fields which allow the DOS library to associate an AmigaDOS environment to the task. Some elements of a DOS environment include a current input and output stream and a current working directory. These elements are important to applications that need to do standard input and output using functions like printf().&lt;br /&gt;
&lt;br /&gt;
Exec only pays attention to the Task structure portion of the Process structure, so, as far as Exec is concerned, there is no difference between a task with a Task structure and a task with a Process structure. Exec considers both of them to be tasks.&lt;br /&gt;
&lt;br /&gt;
An application doesn&#039;t normally worry about which structure their task uses. Instead, the system that launches the application takes care of it. Both Workbench and the Shell (CLI) attach a Process structure to the application tasks that they launch.&lt;br /&gt;
&lt;br /&gt;
== Dynamic Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
The Amiga has a soft machine architecture, meaning that all tasks, including those that are part of its operating system, do not use fixed memory addresses. As a result, any program that needs to use a chunk of memory must allocate that memory from the operating system.&lt;br /&gt;
&lt;br /&gt;
There is one function on the Amiga for simple memory allocation: AllocVecTags(). This function accept the a uint32 containing the size of the memory block in bytes followed by a TagItem array for memory attributes. The function returns the address of a memory block aligned to your specifications if successful or NULL if something went wrong.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any tags when allocating memory, the system looks for MEMF_PRIVATE memory. There are additional memory allocation tags: MEMF_SHARED and MEMF_EXECUTABLE. See the [[Exec_Memory_Allocation|Exec Memory Allocation]] section for additional information on attributes and tags.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Make Sure You Have Memory.&#039;&#039; Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
FreeVec() releases memory allocated by AllocVecTags(). It takes only one parameter, a pointer to a memory block allocated by AllocVecTags(). The following example shows how to allocate and deallocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR my_mem = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (my_mem != NULL)&lt;br /&gt;
{&lt;br /&gt;
    /* Your code goes here */&lt;br /&gt;
    IExec-&amp;gt;FreeVec(my_mem);&lt;br /&gt;
}&lt;br /&gt;
else  { /* couldn&#039;t get memory, exit with an error */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Signals ==&lt;br /&gt;
&lt;br /&gt;
The Amiga uses a mechanism called &#039;&#039;signals&#039;&#039; to tell a task that some event occurred. Each task has its own set of 32 signals, 16 of which are set aside for system use. When one task signals a second task, it asks the OS to set a specific bit in the 32-bit long word set aside for the second task&#039;s signals.&lt;br /&gt;
&lt;br /&gt;
Signals are what makes it possible for a task to go to sleep. When a task goes to sleep, it asks the OS to wake it up when a specific signal bit gets set. That bit is tied to some event. When that event occurs, that signal bit gets set. This triggers the OS into waking up the sleeping task.&lt;br /&gt;
&lt;br /&gt;
To go to sleep, a task calls a system function called Wait(). This function takes one argument, a bitmask that tells Exec which of the task&#039;s signal bits to &amp;quot;listen to&amp;quot;. The task will only wake up if it receives one of the signals whose corresponding bit is set in that bitmask. For example, if a task wanted wait for signals 17 and 19, it would call Wait() like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
mysignals = IExec-&amp;gt;Wait(1U &amp;lt;&amp;lt; 17 | 1U &amp;lt;&amp;lt; 19);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wait() puts the task to sleep and will not return until some other task sets at least one of these two signals. When the task wakes up, mysignals will contain the bitmask of the signal or signals that woke up the task. It is possible for several signals to occur simultaneously, so any combination of the signals that the task Wait()ed on can occur. It is up to the waking task to use the return value from Wait() to figure out which signal or signals occurred.&lt;br /&gt;
&lt;br /&gt;
More information on signals can be found in [[Exec_Signals|Exec Signals]].&lt;br /&gt;
&lt;br /&gt;
== Interprocess Communications ==&lt;br /&gt;
&lt;br /&gt;
Another feature of the Amiga OS is its system of message-based &#039;&#039;interprocess communication&#039;&#039;. Using this system, a task can send a message to a message port owned by another task. Tasks use this mechanism to do things like trigger events or share data with other tasks, including system tasks. Exec&#039;s message system is built on top of Exec&#039;s task signaling mechanism. Most Amiga applications programming (especially Intuition programming) relies heavily upon this message-based form of interprocess communication.&lt;br /&gt;
&lt;br /&gt;
When one task sends a message to another task&#039;s message port, the OS adds the message to the port&#039;s message queue. The message stays in this queue until the task that owns the port is ready to check its port for messages. Typically, a task has put itself to sleep while it is waiting for an event, like a message to arrive at its message port. When the message arrives, the task wakes up to look in its message port. The messages in the message port&#039;s queue are arranged in first-in-first-out (FIFO) order so that, when a task receives several messages, it will see the messages in the order they arrived at the&lt;br /&gt;
&lt;br /&gt;
A task can use a message to share any kind of data with another task. This is possible because a task does not actually transmit an entire message, it only passes a pointer to a message. When a task creates a message (which can have many Kilobytes of data attached to it) and sends it to another task, the actual message does not move.&lt;br /&gt;
&lt;br /&gt;
Essentially, when &#039;&#039;task A&#039;&#039; sends a message to &#039;&#039;task B&#039;&#039;, &#039;&#039;task A&#039;&#039; lends &#039;&#039;task B&#039;&#039; a chunk of its memory, the memory occupied by the message. After &#039;&#039;task A&#039;&#039; sends the message, it has relinquished that memory to &#039;&#039;task B&#039;&#039;, so it cannot touch the memory occupied by the message. &#039;&#039;Task B&#039;&#039; has control of that memory until &#039;&#039;task B&#039;&#039; returns the message to &#039;&#039;task A&#039;&#039; with the ReplyMsg() function.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s look at an example. Many applications use Intuition windows as a source for user input. Without getting into too much detail about Intuition, when an application opens a window, it can set up the window so Intuition will send messages about certain user input events. Intuition sends these messages to a message port created by Intuition for use with this window. When an application successfully opens a window, it receives a pointer to a Window structure, which contains a pointer to this message port (Window.UserPort). For this example, we&#039;ll assume the window has been set up so Intuition will send a message only if the user clicks the window&#039;s close gadget.&lt;br /&gt;
&lt;br /&gt;
When Intuition opens the window in this example, it creates a message port for the task that opened the Window. Because the most common message passing system uses signals, creating this message port involves using one of the example task&#039;s 32 signals. The OS uses this signal to signal the task when it receives a message at this message port. This allows the task to sleep while waiting for a &amp;quot;Close Window&amp;quot; event to arrive. Since this simple example is only waiting for activity at one message port, it can use the WaitPort() function. WaitPort() accepts a pointer to a message port and puts a task to sleep until a message arrives at that port.&lt;br /&gt;
&lt;br /&gt;
This simple example needs two variables, one to hold the address of the window and the other to hold the address of a message.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Message *mymsg; /*defined in &amp;lt;exec/ports.h&amp;gt; */&lt;br /&gt;
struct Window *mywin;  /* defined in &amp;lt;intuition/intuition.h&amp;gt; */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* at some point, this application would have successfully opened a */&lt;br /&gt;
/* window and stored a pointer to it in mywin.                      */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    /* Here the application goes to sleep until the user clicks the window&#039;s close    */&lt;br /&gt;
    /* gadget.  This window was set up so that the only time Intuition will send a    */&lt;br /&gt;
    /* message to this window&#039;s port is if the user clicks the window&#039;s close gadget. */&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;WaitPort(mywin-&amp;gt;UserPort);&lt;br /&gt;
    while (mymsg = IExec-&amp;gt;GetMsg(mywin-&amp;gt;UserPort))&lt;br /&gt;
        /* process message now or copy information from message */&lt;br /&gt;
        IExec-&amp;gt;ReplyMsg(mymsg);&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* Close window, clean up */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Exec function GetMsg() is used to extract messages from the message port. Since the memory for these messages belongs to Intuition, the example relinquishes each message as it finishes with them by calling ReplyMsg(). Notice that the example keeps on trying to get messages from the message port until mymsg is NULL. This is to make sure there are no messages left in the message port&#039;s message queue. It is possible for several messages to pile up in the message queue while the task is asleep, so the example has to make sure it replies to all of them. Note that the window should never be closed within this GetMsg() loop because the while statement is still accessing the window&#039;s UserPort.&lt;br /&gt;
&lt;br /&gt;
Note that each task with a Process structure (sometimes referred to as a process) has a special process message port, Process.pr_MsgPort. This message port is only for use by Workbench and the DOS library itself. &#039;&#039;No application should use this port for its own use!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
More detailed information about message ports can be found in [[Exec_Messages_and_Ports|Exec Messages and Ports]].&lt;br /&gt;
&lt;br /&gt;
==== Waiting on Message Ports and Signals at the Same Time ====&lt;br /&gt;
&lt;br /&gt;
Most applications need to wait for a variety of messages and signals from a variety of sources. For example, an application might be waiting for Window events and also timer.device messages. In this case, an application must Wait() on the combined signal bits of all signals it is interested in, including the signals for the message ports where any messages might arrive.&lt;br /&gt;
&lt;br /&gt;
The MsgPort structure, which is defined in &amp;amp;lt;exec/ports.h&amp;amp;gt;, is what Exec uses to keep track of a message port. The UserPort field from the example above points to one of these structures. In this structure is a field called mp_SigBit, which contains the &#039;&#039;number&#039;&#039; (not the actual bit mask) of the message port&#039;s signal bit. To Wait() on the signal of a message port, Wait() on a bit mask created by shifting 1U to the left mp_SigBit times (1U &amp;lt;&amp;lt; msgport-&amp;amp;gt;mp_SigBit). The resulting bit mask can be &#039;&#039;&#039;OR&#039;&#039;&#039;&#039;d with the bit masks for any other signals you wish to simultaneously wait on.&lt;br /&gt;
&lt;br /&gt;
== Libraries and Devices ==&lt;br /&gt;
&lt;br /&gt;
One of the design goals for the Amiga OS was to make the system dynamic enough so that the OS could be extended and updated without effecting existing applications. Another design goal was to make it easy for different applications to be able to share common pieces of code. The Amiga accomplished these goals through a system of libraries. An Amiga library consists of a collection of related functions which can be anywhere in system memory (RAM or ROM).&lt;br /&gt;
&lt;br /&gt;
Devices are very similar to libraries, except they usually control some sort of I/O device and contain some extra standard functions. Although this section does not really discuss devices directly, the material here applies to them. For more information on devices, see the [[Exec Device I/O]] section of this manual or the [[Devices|Devices]].&lt;br /&gt;
&lt;br /&gt;
An application accesses a library&#039;s functions through interfaces. Each library exports one or more interfaces. Before a task can use the functions of a particular interface, it must first acquire the library&#039;s base pointer by calling the Exec OpenLibrary() function and then obtain an interface pointer by calling GetInterface().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Library *OpenLibrary( CONST_STRPTR libName, uint32 libVer );&lt;br /&gt;
struct Interface *GetInterface( struct Library *base, CONST_STRPTR ifaceName, uint32 ifaceVer, struct TagItem *tags);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where libName is a string naming the library and libVer is a version number for the library. The library base pointer is passed on to GetInterface() along with ifaceName which is the name of the interface and ifaceVer which is the version. An optional tag array completes the call.&lt;br /&gt;
&lt;br /&gt;
The libVer number reflects a revision of the system software. The chart below lists the specific Amiga OS release versions that system libraries versions correspond to:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 30 || Kickstart 1.0 || This revision is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 31 || Kickstart 1.1 || This was an NTSC only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 32 || Kickstart 1.1 || This was a PAL only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 33 || Kickstart 1.2 ||This is the oldest revision of the OS still in use.&lt;br /&gt;
|-&lt;br /&gt;
| 34 || Kickstart 1.3 || This is almost the same as release 1.2 except it has an Autobooting expansion.library&lt;br /&gt;
|-&lt;br /&gt;
| 35 || || This is a special RAM-loaded version of the 1.3 revision, except that it knows about the &#039;&#039;A2024&#039;&#039; display modes. No application should need this library unless they need to open an &#039;&#039;A2024&#039;&#039; display mode under 1.3.&lt;br /&gt;
|-&lt;br /&gt;
| 36 || Kickstart 2.0 || This is the original Release 2 revision that was initially shipped on early &#039;&#039;Amiga 3000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 37 || Kickstart 2.04 || This is the general Release 2 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 38 || Kickstart 2.1 || This is an updated Release 2 revision for &#039;&#039;Amiga 600&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 39 || Kickstart 3.0 || This is the original Release 3 revision hat was initially shipped on &#039;&#039;Amiga 1200&#039;&#039; and &#039;&#039;Amiga 4000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 40 || Kickstart 3.1 || This is the general Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 44 || Kickstart 3.5 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 45 || Kickstart 3.9 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 50 || Kickstart 4.0 || This is the general Release 4 revision for PPC equipped Amiga models and the new &#039;&#039;AmigaOne&#039;&#039; range.&lt;br /&gt;
|-&lt;br /&gt;
| 51 || || &lt;br /&gt;
|-&lt;br /&gt;
| 52 || Kickstart 4.0 || This is an update to Release 4 revision to include support for PPC equipped classic Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 53 || Kickstart 4.1 || This is an update to Release 4 revision for all supported models.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The OpenLibrary() function looks for a library with a name that matches libName and also with a version at least as high as libVersion. For example, to open version 50 or greater of the Intuition library:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example, if version 50 or greater of the Intuition library is not available, OpenLibrary() returns NULL. A version of zero in OpenLibrary() tells the OS to open any version of the library. New code should use a version of 50 or higher unless noted otherwise.&lt;br /&gt;
&lt;br /&gt;
When OpenLibrary() looks for a library, it first looks in memory. If the library is not in memory, OpenLibrary() will look for the library on disk. If libName is a library name with an absolute path (for example, &amp;quot;myapp:mylibs/mylib.library&amp;quot;), OpenLibrary() will follow that absolute path looking for the library. If libName is only a library name (&amp;quot;diskfont.library&amp;quot;), OpenLibrary() will look for the library in the directory that the LIBS: logical assign currently references.&lt;br /&gt;
&lt;br /&gt;
If OpenLibrary() finds the library on disk, it takes care of loading it and initializing it. As part of the initialization process, OpenLibrary() dynamically creates a vector table. There is a vector for each function in the library. The OS needs to create the vector table dynamically because the library functions can be anywhere in memory.&lt;br /&gt;
&lt;br /&gt;
After the library is loaded into memory and initialized, OpenLibrary() can actually &amp;quot;open&amp;quot; the library. It does this by calling the library&#039;s Open function vector. Every library has a standard vector set aside for an OPEN function so the library can set up any data or processes that it needs. Normally, a library&#039;s OPEN function increments its open count to keep track of how many tasks currently have the library opened.&lt;br /&gt;
&lt;br /&gt;
If any step of OpenLibrary() fails, it returns a NULL value. If OpenLibrary() is successful, it returns the address of the library base. The library base is the address of this library&#039;s Library structure (defined in &amp;quot;exec/libraries.h&amp;quot;). The Library structure immediately follows the vector table in memory.&lt;br /&gt;
&lt;br /&gt;
Once the library has been opened the application needs to obtain access to the interfaces with GetInterface() in order to call the functions available. Every task or process is required to call GetInterface() on the instance and should not share the interface pointer.&lt;br /&gt;
&lt;br /&gt;
After an application is finished with a library, it &#039;&#039;must&#039;&#039; close it by calling DropInterface() on each interface and finally CloseLibrary():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
VOID DropInterface(struct Interface *ifacePtr);&lt;br /&gt;
VOID CloseLibrary(struct Library *libPtr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where ifacePtr is a pointer to an interface and libPtr is a pointer to the library base returned when the library was opened with OpenLibrary().&lt;br /&gt;
&lt;br /&gt;
For more detailed information about libraries and interfaces see [[Exec_Libraries|Exec Libraries]].&lt;br /&gt;
&lt;br /&gt;
=== Calling a Library Function ===&lt;br /&gt;
&lt;br /&gt;
To call a function in an Amiga system library a pointer to an interface is required. For example to call the Intuition function DisplayBeep():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IntuitionIFace *IIntuition = IExec-&amp;gt;GetInterface(base, &amp;quot;main&amp;quot; 1, NULL);&lt;br /&gt;
&lt;br /&gt;
IIntuition-&amp;gt;DisplayBeep();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Function parameters in C are pushed on the stack when a program calls a function. The order in which parameters are pushed is defined by the [http://en.wikipedia.org/wiki/Executable_and_Linkable_Format SysV ABI specification].&lt;br /&gt;
&lt;br /&gt;
For more detailed information about calling library functions see [[Exec_Libraries|Exec Libraries]].&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3387</id>
		<title>Introduction to Exec</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3387"/>
		<updated>2012-07-08T09:26:29Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Dynamic Memory Allocation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to Exec ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;Multitasking Executive&#039;&#039;, better known as &#039;&#039;Exec&#039;&#039;, is the heart of the Amiga&#039;s operating system. All other systems in the Amiga rely on it to control multitasking, to manage the message-based interprocess communications system, and to arbitrate access to system resources. Because just about every software entity on the Amiga (including application programs) needs to use Exec in some way, every Amiga programmer has to have a basic understanding of its fundamentals.&lt;br /&gt;
&lt;br /&gt;
== Multitasking ==&lt;br /&gt;
&lt;br /&gt;
A conventional micro-computer spends a lot of its time waiting for things to happen. It has to wait for such things as the user to push buttons on the keyboard or mouse, for data to come in through the serial port, and for data to go out to a disk drive. To make efficient use of the CPU&#039;s time, an operating system can have the CPU carry out some other task while it is waiting for such events to occur.&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;multitasking&#039;&#039; operating system reduces the amount of time it wastes, by switching to another program when the current one needs to wait for an event. A multitasking operating system can have several programs, or tasks, running at the same time. Each task runs independently of the others, without having to worry about what the other tasks are doing. From a task&#039;s point of view, it&#039;s as if each task has a computer all to itself.&lt;br /&gt;
&lt;br /&gt;
The Amiga&#039;s multitasking works by switching which task is currently using the CPU. A task can be a user&#039;s application program, or it can be a task which controls system resources (like the disk drives or the keyboard). Each task has a priority assigned to it. Exec will let the task with the highest priority use the CPU, but only if the task is ready to run. A task can be in one of three states: &#039;&#039;ready&#039;&#039;, &#039;&#039;sleeping&#039;&#039;, or &#039;&#039;running&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A ready task is not currently using the CPU but is waiting to use the processor. Exec keeps a list of the tasks that are ready. Exec sorts this list according to task priority, so Exec can easily find the ready task with the highest priority. When Exec switches the task that currently has control of the CPU, it switches to the task at the top of this list.&lt;br /&gt;
&lt;br /&gt;
A sleeping task is not currently running and is waiting for some event to happen. When that event occurs, Exec will move the sleeping task into the list of ready tasks.&lt;br /&gt;
&lt;br /&gt;
A running task is currently using the CPU. It will remain the current task until one of three things occur:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;A higher priority task becomes ready, so the OS preempts the current task and switches to the higher priority task.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task needs to wait for an event, so it goes to sleep and Exec switches to the highest priority task in Exec&#039;s ready list.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task has had control of the CPU for at least a preset time period called a &#039;&#039;quantum&#039;&#039; and there is another task of equal priority ready to run. In this case, Exec will preempt the current task for the ready one with the same priority. This is known as &#039;&#039;time-slicing&#039;&#039;. When there is a group of tasks of equal priority on the top of the ready list, Exec will cycle through them, letting each one use the CPU for a quantum (a slice of time).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The terms &amp;quot;task&amp;quot; and &amp;quot;process&amp;quot; are often used interchangeably to represent the generic concept of task. On the Amiga, this terminology can be a little confusing because of the names of the data structures that are associated with Exec tasks. Each task has a structure associated with it called a Task structure (defined in &amp;amp;lt;exec/tasks.h&amp;amp;gt;). Most application tasks use a superset of the Task structure called a Process structure (defined in &amp;amp;lt;dos/dosextens.h&amp;amp;gt;). These terms are confusing to Amiga programmers because there is an important distinction between the Exec task with only a Task structure and an Exec task with a Process structure.&lt;br /&gt;
&lt;br /&gt;
The Process structure builds on the Task structure and contains some extra fields which allow the DOS library to associate an AmigaDOS environment to the task. Some elements of a DOS environment include a current input and output stream and a current working directory. These elements are important to applications that need to do standard input and output using functions like printf().&lt;br /&gt;
&lt;br /&gt;
Exec only pays attention to the Task structure portion of the Process structure, so, as far as Exec is concerned, there is no difference between a task with a Task structure and a task with a Process structure. Exec considers both of them to be tasks.&lt;br /&gt;
&lt;br /&gt;
An application doesn&#039;t normally worry about which structure their task uses. Instead, the system that launches the application takes care of it. Both Workbench and the Shell (CLI) attach a Process structure to the application tasks that they launch.&lt;br /&gt;
&lt;br /&gt;
== Dynamic Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
The Amiga has a soft machine architecture, meaning that all tasks, including those that are part of its operating system, do not use fixed memory addresses. As a result, any program that needs to use a chunk of memory must allocate that memory from the operating system.&lt;br /&gt;
&lt;br /&gt;
There is one function on the Amiga for simple memory allocation: AllocVecTags(). This function accept the a uint32 containing the size of the memory block in bytes followed by a TagItem array for memory attributes. The function returns the address of a memory block aligned to your specifications if successful or NULL if something went wrong.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any tags when allocating memory, the system looks for MEMF_PRIVATE memory. There are additional memory allocation tags: MEMF_SHARED and MEMF_EXECUTABLE. See the [[Exec_Memory_Allocation|Exec Memory Allocation]] section for additional information on attributes and tags.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Make Sure You Have Memory.&#039;&#039; Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
FreeVec() releases memory allocated by AllocVecTags(). It takes only one parameter, a pointer to a memory block allocated by AllocVecTags(). The following example shows how to allocate and deallocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR my_mem = IExec-&amp;gt;AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (my_mem != NULL)&lt;br /&gt;
{&lt;br /&gt;
    /* Your code goes here */&lt;br /&gt;
    IExec-&amp;gt;FreeVec(my_mem);&lt;br /&gt;
}&lt;br /&gt;
else  { /* couldn&#039;t get memory, exit with an error */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Signals ==&lt;br /&gt;
&lt;br /&gt;
The Amiga uses a mechanism called &#039;&#039;signals&#039;&#039; to tell a task that some event occurred. Each task has its own set of 32 signals, 16 of which are set aside for system use. When one task signals a second task, it asks the OS to set a specific bit in the 32-bit long word set aside for the second task&#039;s signals.&lt;br /&gt;
&lt;br /&gt;
Signals are what makes it possible for a task to go to sleep. When a task goes to sleep, it asks the OS to wake it up when a specific signal bit gets set. That bit is tied to some event. When that event occurs, that signal bit gets set. This triggers the OS into waking up the sleeping task.&lt;br /&gt;
&lt;br /&gt;
To go to sleep, a task calls a system function called Wait(). This function takes one argument, a bitmask that tells Exec which of the task&#039;s signal bits to &amp;quot;listen to&amp;quot;. The task will only wake up if it receives one of the signals whose corresponding bit is set in that bitmask. For example, if a task wanted to wait for signals 17 and 19, it would call Wait() like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
mysignals = IExec-&amp;gt;Wait(1U &amp;lt;&amp;lt; 17 | 1U &amp;lt;&amp;lt; 19);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wait() puts the task to sleep and will not return until some other task sets at least one of these two signals. When the task wakes up, mysignals will contain the bitmask of the signal or signals that woke up the task. It is possible for several signals to occur simultaneously, so any combination of the signals that the task Wait()ed on can occur. It is up to the waking task to use the return value from Wait() to figure out which signal or signals occurred.&lt;br /&gt;
&lt;br /&gt;
More information on signals can be found in [[Exec_Signals|Exec Signals]].&lt;br /&gt;
&lt;br /&gt;
== Interprocess Communications ==&lt;br /&gt;
&lt;br /&gt;
Another feature of the Amiga OS is its system of message-based &#039;&#039;interprocess communication&#039;&#039;. Using this system, a task can send a message to a message port owned by another task. Tasks use this mechanism to do things like trigger events or share data with other tasks, including system tasks. Exec&#039;s message system is built on top of Exec&#039;s task signaling mechanism. Most Amiga applications programming (especially Intuition programming) relies heavily upon this message-based form of interprocess communication.&lt;br /&gt;
&lt;br /&gt;
When one task sends a message to another task&#039;s message port, the OS adds the message to the port&#039;s message queue. The message stays in this queue until the task that owns the port is ready to check its port for messages. Typically, a task has put itself to sleep while it is waiting for an event, like a message to arrive at its message port. When the message arrives, the task wakes up to look in its message port. The messages in the message port&#039;s queue are arranged in first-in-first-out (FIFO) order so that, when a task receives several messages, it will see the messages in the order they arrived at the&lt;br /&gt;
&lt;br /&gt;
A task can use a message to share any kind of data with another task. This is possible because a task does not actually transmit an entire message, it only passes a pointer to a message. When a task creates a message (which can have many Kilobytes of data attached to it) and sends it to another task, the actual message does not move.&lt;br /&gt;
&lt;br /&gt;
Essentially, when &#039;&#039;task A&#039;&#039; sends a message to &#039;&#039;task B&#039;&#039;, &#039;&#039;task A&#039;&#039; lends &#039;&#039;task B&#039;&#039; a chunk of its memory, the memory occupied by the message. After &#039;&#039;task A&#039;&#039; sends the message, it has relinquished that memory to &#039;&#039;task B&#039;&#039;, so it cannot touch the memory occupied by the message. &#039;&#039;Task B&#039;&#039; has control of that memory until &#039;&#039;task B&#039;&#039; returns the message to &#039;&#039;task A&#039;&#039; with the ReplyMsg() function.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s look at an example. Many applications use Intuition windows as a source for user input. Without getting into too much detail about Intuition, when an application opens a window, it can set up the window so Intuition will send messages about certain user input events. Intuition sends these messages to a message port created by Intuition for use with this window. When an application successfully opens a window, it receives a pointer to a Window structure, which contains a pointer to this message port (Window.UserPort). For this example, we&#039;ll assume the window has been set up so Intuition will send a message only if the user clicks the window&#039;s close gadget.&lt;br /&gt;
&lt;br /&gt;
When Intuition opens the window in this example, it creates a message port for the task that opened the Window. Because the most common message passing system uses signals, creating this message port involves using one of the example task&#039;s 32 signals. The OS uses this signal to signal the task when it receives a message at this message port. This allows the task to sleep while waiting for a &amp;quot;Close Window&amp;quot; event to arrive. Since this simple example is only waiting for activity at one message port, it can use the WaitPort() function. WaitPort() accepts a pointer to a message port and puts a task to sleep until a message arrives at that port.&lt;br /&gt;
&lt;br /&gt;
This simple example needs two variables, one to hold the address of the window and the other to hold the address of a message.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Message *mymsg; /*defined in &amp;lt;exec/ports.h&amp;gt; */&lt;br /&gt;
struct Window *mywin;  /* defined in &amp;lt;intuition/intuition.h&amp;gt; */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* at some point, this application would have successfully opened a */&lt;br /&gt;
/* window and stored a pointer to it in mywin.                      */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    /* Here the application goes to sleep until the user clicks the window&#039;s close    */&lt;br /&gt;
    /* gadget.  This window was set up so that the only time Intuition will send a    */&lt;br /&gt;
    /* message to this window&#039;s port is if the user clicks the window&#039;s close gadget. */&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;WaitPort(mywin-&amp;gt;UserPort);&lt;br /&gt;
    while (mymsg = IExec-&amp;gt;GetMsg(mywin-&amp;gt;UserPort))&lt;br /&gt;
        /* process message now or copy information from message */&lt;br /&gt;
        IExec-&amp;gt;ReplyMsg(mymsg);&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* Close window, clean up */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Exec function GetMsg() is used to extract messages from the message port. Since the memory for these messages belongs to Intuition, the example relinquishes each message as it finishes with them by calling ReplyMsg(). Notice that the example keeps on trying to get messages from the message port until mymsg is NULL. This is to make sure there are no messages left in the message port&#039;s message queue. It is possible for several messages to pile up in the message queue while the task is asleep, so the example has to make sure it replies to all of them. Note that the window should never be closed within this GetMsg() loop because the while statement is still accessing the window&#039;s UserPort.&lt;br /&gt;
&lt;br /&gt;
Note that each task with a Process structure (sometimes referred to as a process) has a special process message port, Process.pr_MsgPort. This message port is only for use by Workbench and the DOS library itself. &#039;&#039;No application should use this port for its own use!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
More detailed information about message ports can be found in [[Exec_Messages_and_Ports|Exec Messages and Ports]].&lt;br /&gt;
&lt;br /&gt;
==== Waiting on Message Ports and Signals at the Same Time ====&lt;br /&gt;
&lt;br /&gt;
Most applications need to wait for a variety of messages and signals from a variety of sources. For example, an application might be waiting for Window events and also timer.device messages. In this case, an application must Wait() on the combined signal bits of all signals it is interested in, including the signals for the message ports where any messages might arrive.&lt;br /&gt;
&lt;br /&gt;
The MsgPort structure, which is defined in &amp;amp;lt;exec/ports.h&amp;amp;gt;, is what Exec uses to keep track of a message port. The UserPort field from the example above points to one of these structures. In this structure is a field called mp_SigBit, which contains the &#039;&#039;number&#039;&#039; (not the actual bit mask) of the message port&#039;s signal bit. To Wait() on the signal of a message port, Wait() on a bit mask created by shifting 1U to the left mp_SigBit times (1U &amp;lt;&amp;lt; msgport-&amp;amp;gt;mp_SigBit). The resulting bit mask can be &#039;&#039;&#039;OR&#039;&#039;&#039;&#039;d with the bit masks for any other signals you wish to simultaneously wait on.&lt;br /&gt;
&lt;br /&gt;
== Libraries and Devices ==&lt;br /&gt;
&lt;br /&gt;
One of the design goals for the Amiga OS was to make the system dynamic enough so that the OS could be extended and updated without effecting existing applications. Another design goal was to make it easy for different applications to be able to share common pieces of code. The Amiga accomplished these goals through a system of libraries. An Amiga library consists of a collection of related functions which can be anywhere in system memory (RAM or ROM).&lt;br /&gt;
&lt;br /&gt;
Devices are very similar to libraries, except they usually control some sort of I/O device and contain some extra standard functions. Although this section does not really discuss devices directly, the material here applies to them. For more information on devices, see the [[Exec Device I/O]] section of this manual or the [[Devices|Devices]].&lt;br /&gt;
&lt;br /&gt;
An application accesses a library&#039;s functions through interfaces. Each library exports one or more interfaces. Before a task can use the functions of a particular interface, it must first acquire the library&#039;s base pointer by calling the Exec OpenLibrary() function and then obtain an interface pointer by calling GetInterface().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Library *OpenLibrary( CONST_STRPTR libName, uint32 libVer );&lt;br /&gt;
struct Interface *GetInterface( struct Library *base, CONST_STRPTR ifaceName, uint32 ifaceVer, struct TagItem *tags);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where libName is a string naming the library and libVer is a version number for the library. The library base pointer is passed on to GetInterface() along with ifaceName which is the name of the interface and ifaceVer which is the version. An optional tag array completes the call.&lt;br /&gt;
&lt;br /&gt;
The libVer number reflects a revision of the system software. The chart below lists the specific Amiga OS release versions that system libraries versions correspond to:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 30 || Kickstart 1.0 || This revision is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 31 || Kickstart 1.1 || This was an NTSC only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 32 || Kickstart 1.1 || This was a PAL only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 33 || Kickstart 1.2 ||This is the oldest revision of the OS still in use.&lt;br /&gt;
|-&lt;br /&gt;
| 34 || Kickstart 1.3 || This is almost the same as release 1.2 except it has an Autobooting expansion.library&lt;br /&gt;
|-&lt;br /&gt;
| 35 || || This is a special RAM-loaded version of the 1.3 revision, except that it knows about the &#039;&#039;A2024&#039;&#039; display modes. No application should need this library unless they need to open an &#039;&#039;A2024&#039;&#039; display mode under 1.3.&lt;br /&gt;
|-&lt;br /&gt;
| 36 || Kickstart 2.0 || This is the original Release 2 revision that was initially shipped on early &#039;&#039;Amiga 3000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 37 || Kickstart 2.04 || This is the general Release 2 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 38 || Kickstart 2.1 || This is an updated Release 2 revision for &#039;&#039;Amiga 600&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 39 || Kickstart 3.0 || This is the original Release 3 revision hat was initially shipped on &#039;&#039;Amiga 1200&#039;&#039; and &#039;&#039;Amiga 4000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 40 || Kickstart 3.1 || This is the general Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 44 || Kickstart 3.5 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 45 || Kickstart 3.9 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 50 || Kickstart 4.0 || This is the general Release 4 revision for PPC equipped Amiga models and the new &#039;&#039;AmigaOne&#039;&#039; range.&lt;br /&gt;
|-&lt;br /&gt;
| 51 || || &lt;br /&gt;
|-&lt;br /&gt;
| 52 || Kickstart 4.0 || This is an update to Release 4 revision to include support for PPC equipped classic Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 53 || Kickstart 4.1 || This is an update to Release 4 revision for all supported models.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The OpenLibrary() function looks for a library with a name that matches libName and also with a version at least as high as libVersion. For example, to open version 50 or greater of the Intuition library:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example, if version 50 or greater of the Intuition library is not available, OpenLibrary() returns NULL. A version of zero in OpenLibrary() tells the OS to open any version of the library. New code should use a version of 50 or higher unless noted otherwise.&lt;br /&gt;
&lt;br /&gt;
When OpenLibrary() looks for a library, it first looks in memory. If the library is not in memory, OpenLibrary() will look for the library on disk. If libName is a library name with an absolute path (for example, &amp;quot;myapp:mylibs/mylib.library&amp;quot;), OpenLibrary() will follow that absolute path looking for the library. If libName is only a library name (&amp;quot;diskfont.library&amp;quot;), OpenLibrary() will look for the library in the directory that the LIBS: logical assign currently references.&lt;br /&gt;
&lt;br /&gt;
If OpenLibrary() finds the library on disk, it takes care of loading it and initializing it. As part of the initialization process, OpenLibrary() dynamically creates a vector table. There is a vector for each function in the library. The OS needs to create the vector table dynamically because the library functions can be anywhere in memory.&lt;br /&gt;
&lt;br /&gt;
After the library is loaded into memory and initialized, OpenLibrary() can actually &amp;quot;open&amp;quot; the library. It does this by calling the library&#039;s Open function vector. Every library has a standard vector set aside for an OPEN function so the library can set up any data or processes that it needs. Normally, a library&#039;s OPEN function increments its open count to keep track of how many tasks currently have the library opened.&lt;br /&gt;
&lt;br /&gt;
If any step of OpenLibrary() fails, it returns a NULL value. If OpenLibrary() is successful, it returns the address of the library base. The library base is the address of this library&#039;s Library structure (defined in &amp;quot;exec/libraries.h&amp;quot;). The Library structure immediately follows the vector table in memory.&lt;br /&gt;
&lt;br /&gt;
Once the library has been opened the application needs to obtain access to the interfaces with GetInterface() in order to call the functions available. Every task or process is required to call GetInterface() on the instance and should not share the interface pointer.&lt;br /&gt;
&lt;br /&gt;
After an application is finished with a library, it &#039;&#039;must&#039;&#039; close it by calling DropInterface() on each interface and finally CloseLibrary():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
VOID DropInterface(struct Interface *ifacePtr);&lt;br /&gt;
VOID CloseLibrary(struct Library *libPtr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where ifacePtr is a pointer to an interface and libPtr is a pointer to the library base returned when the library was opened with OpenLibrary().&lt;br /&gt;
&lt;br /&gt;
For more detailed information about libraries and interfaces see [[Exec_Libraries|Exec Libraries]].&lt;br /&gt;
&lt;br /&gt;
=== Calling a Library Function ===&lt;br /&gt;
&lt;br /&gt;
To call a function in an Amiga system library a pointer to an interface is required. For example to call the Intuition function DisplayBeep():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IntuitionIFace *IIntuition = IExec-&amp;gt;GetInterface(base, &amp;quot;main&amp;quot; 1, NULL);&lt;br /&gt;
&lt;br /&gt;
IIntuition-&amp;gt;DisplayBeep();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Function parameters in C are pushed on the stack when a program calls a function. The order in which parameters are pushed is defined by the [http://en.wikipedia.org/wiki/Executable_and_Linkable_Format SysV ABI specification].&lt;br /&gt;
&lt;br /&gt;
For more detailed information about calling library functions see [[Exec_Libraries|Exec Libraries]].&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3386</id>
		<title>Introduction to Exec</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=Introduction_to_Exec&amp;diff=3386"/>
		<updated>2012-07-08T09:21:14Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Multitasking */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to Exec ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;Multitasking Executive&#039;&#039;, better known as &#039;&#039;Exec&#039;&#039;, is the heart of the Amiga&#039;s operating system. All other systems in the Amiga rely on it to control multitasking, to manage the message-based interprocess communications system, and to arbitrate access to system resources. Because just about every software entity on the Amiga (including application programs) needs to use Exec in some way, every Amiga programmer has to have a basic understanding of its fundamentals.&lt;br /&gt;
&lt;br /&gt;
== Multitasking ==&lt;br /&gt;
&lt;br /&gt;
A conventional micro-computer spends a lot of its time waiting for things to happen. It has to wait for such things as the user to push buttons on the keyboard or mouse, for data to come in through the serial port, and for data to go out to a disk drive. To make efficient use of the CPU&#039;s time, an operating system can have the CPU carry out some other task while it is waiting for such events to occur.&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;multitasking&#039;&#039; operating system reduces the amount of time it wastes, by switching to another program when the current one needs to wait for an event. A multitasking operating system can have several programs, or tasks, running at the same time. Each task runs independently of the others, without having to worry about what the other tasks are doing. From a task&#039;s point of view, it&#039;s as if each task has a computer all to itself.&lt;br /&gt;
&lt;br /&gt;
The Amiga&#039;s multitasking works by switching which task is currently using the CPU. A task can be a user&#039;s application program, or it can be a task which controls system resources (like the disk drives or the keyboard). Each task has a priority assigned to it. Exec will let the task with the highest priority use the CPU, but only if the task is ready to run. A task can be in one of three states: &#039;&#039;ready&#039;&#039;, &#039;&#039;sleeping&#039;&#039;, or &#039;&#039;running&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A ready task is not currently using the CPU but is waiting to use the processor. Exec keeps a list of the tasks that are ready. Exec sorts this list according to task priority, so Exec can easily find the ready task with the highest priority. When Exec switches the task that currently has control of the CPU, it switches to the task at the top of this list.&lt;br /&gt;
&lt;br /&gt;
A sleeping task is not currently running and is waiting for some event to happen. When that event occurs, Exec will move the sleeping task into the list of ready tasks.&lt;br /&gt;
&lt;br /&gt;
A running task is currently using the CPU. It will remain the current task until one of three things occur:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;A higher priority task becomes ready, so the OS preempts the current task and switches to the higher priority task.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task needs to wait for an event, so it goes to sleep and Exec switches to the highest priority task in Exec&#039;s ready list.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;The currently running task has had control of the CPU for at least a preset time period called a &#039;&#039;quantum&#039;&#039; and there is another task of equal priority ready to run. In this case, Exec will preempt the current task for the ready one with the same priority. This is known as &#039;&#039;time-slicing&#039;&#039;. When there is a group of tasks of equal priority on the top of the ready list, Exec will cycle through them, letting each one use the CPU for a quantum (a slice of time).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The terms &amp;quot;task&amp;quot; and &amp;quot;process&amp;quot; are often used interchangeably to represent the generic concept of task. On the Amiga, this terminology can be a little confusing because of the names of the data structures that are associated with Exec tasks. Each task has a structure associated with it called a Task structure (defined in &amp;amp;lt;exec/tasks.h&amp;amp;gt;). Most application tasks use a superset of the Task structure called a Process structure (defined in &amp;amp;lt;dos/dosextens.h&amp;amp;gt;). These terms are confusing to Amiga programmers because there is an important distinction between the Exec task with only a Task structure and an Exec task with a Process structure.&lt;br /&gt;
&lt;br /&gt;
The Process structure builds on the Task structure and contains some extra fields which allow the DOS library to associate an AmigaDOS environment to the task. Some elements of a DOS environment include a current input and output stream and a current working directory. These elements are important to applications that need to do standard input and output using functions like printf().&lt;br /&gt;
&lt;br /&gt;
Exec only pays attention to the Task structure portion of the Process structure, so, as far as Exec is concerned, there is no difference between a task with a Task structure and a task with a Process structure. Exec considers both of them to be tasks.&lt;br /&gt;
&lt;br /&gt;
An application doesn&#039;t normally worry about which structure their task uses. Instead, the system that launches the application takes care of it. Both Workbench and the Shell (CLI) attach a Process structure to the application tasks that they launch.&lt;br /&gt;
&lt;br /&gt;
== Dynamic Memory Allocation ==&lt;br /&gt;
&lt;br /&gt;
The Amiga has a soft machine architecture, meaning that all tasks, including those that are part of its operating system, do not use fixed memory addresses. As a result, any program that needs to use a chunk of memory must allocate that memory from the operating system.&lt;br /&gt;
&lt;br /&gt;
There is one function on the Amiga for simple memory allocation: AllocVecTags(). This function accept the a uint32 containing the size of the memory block in bytes followed by a TagItem array for memory attributes. The function returns the address of a memory block aligned to your specifications if successful or NULL if something went wrong.&lt;br /&gt;
&lt;br /&gt;
If an application does not specify any tags when allocating memory, the system looks for MEMF_PRIVATE memory. There are additional memory allocation tags: MEMF_SHARED and MEMF_EXECUTABLE. See the [[Exec_Memory_Allocation|Exec Memory Allocation]] section for additional information on attributes and tags.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Make Sure You Have Memory.&#039;&#039; Always check the result of any memory allocation to be sure the type and amount of memory requested is available. Failure to do so will lead to trying to use an non-valid pointer.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
FreeVec() releases memory allocated by AllocVecTags(). It takes only one parameter, a pointer to a memory block allocated by AllocVecTags(). The following example shows how to allocate and deallocate memory.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
APTR my_mem = AllocVecTags(100, TAG_END);&lt;br /&gt;
&lt;br /&gt;
if (my_mem != NULL)&lt;br /&gt;
{&lt;br /&gt;
    /* Your code goes here */&lt;br /&gt;
    IExec-&amp;gt;FreeVec(my_mem);&lt;br /&gt;
}&lt;br /&gt;
else  { /* couldn&#039;t get memory, exit with an error */ }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Signals ==&lt;br /&gt;
&lt;br /&gt;
The Amiga uses a mechanism called &#039;&#039;signals&#039;&#039; to tell a task that some event occurred. Each task has its own set of 32 signals, 16 of which are set aside for system use. When one task signals a second task, it asks the OS to set a specific bit in the 32-bit long word set aside for the second task&#039;s signals.&lt;br /&gt;
&lt;br /&gt;
Signals are what makes it possible for a task to go to sleep. When a task goes to sleep, it asks the OS to wake it up when a specific signal bit gets set. That bit is tied to some event. When that event occurs, that signal bit gets set. This triggers the OS into waking up the sleeping task.&lt;br /&gt;
&lt;br /&gt;
To go to sleep, a task calls a system function called Wait(). This function takes one argument, a bitmask that tells Exec which of the task&#039;s signal bits to &amp;quot;listen to&amp;quot;. The task will only wake up if it receives one of the signals whose corresponding bit is set in that bitmask. For example, if a task wanted to wait for signals 17 and 19, it would call Wait() like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
mysignals = IExec-&amp;gt;Wait(1U &amp;lt;&amp;lt; 17 | 1U &amp;lt;&amp;lt; 19);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wait() puts the task to sleep and will not return until some other task sets at least one of these two signals. When the task wakes up, mysignals will contain the bitmask of the signal or signals that woke up the task. It is possible for several signals to occur simultaneously, so any combination of the signals that the task Wait()ed on can occur. It is up to the waking task to use the return value from Wait() to figure out which signal or signals occurred.&lt;br /&gt;
&lt;br /&gt;
More information on signals can be found in [[Exec_Signals|Exec Signals]].&lt;br /&gt;
&lt;br /&gt;
== Interprocess Communications ==&lt;br /&gt;
&lt;br /&gt;
Another feature of the Amiga OS is its system of message-based &#039;&#039;interprocess communication&#039;&#039;. Using this system, a task can send a message to a message port owned by another task. Tasks use this mechanism to do things like trigger events or share data with other tasks, including system tasks. Exec&#039;s message system is built on top of Exec&#039;s task signaling mechanism. Most Amiga applications programming (especially Intuition programming) relies heavily upon this message-based form of interprocess communication.&lt;br /&gt;
&lt;br /&gt;
When one task sends a message to another task&#039;s message port, the OS adds the message to the port&#039;s message queue. The message stays in this queue until the task that owns the port is ready to check its port for messages. Typically, a task has put itself to sleep while it is waiting for an event, like a message to arrive at its message port. When the message arrives, the task wakes up to look in its message port. The messages in the message port&#039;s queue are arranged in first-in-first-out (FIFO) order so that, when a task receives several messages, it will see the messages in the order they arrived at the&lt;br /&gt;
&lt;br /&gt;
A task can use a message to share any kind of data with another task. This is possible because a task does not actually transmit an entire message, it only passes a pointer to a message. When a task creates a message (which can have many Kilobytes of data attached to it) and sends it to another task, the actual message does not move.&lt;br /&gt;
&lt;br /&gt;
Essentially, when &#039;&#039;task A&#039;&#039; sends a message to &#039;&#039;task B&#039;&#039;, &#039;&#039;task A&#039;&#039; lends &#039;&#039;task B&#039;&#039; a chunk of its memory, the memory occupied by the message. After &#039;&#039;task A&#039;&#039; sends the message, it has relinquished that memory to &#039;&#039;task B&#039;&#039;, so it cannot touch the memory occupied by the message. &#039;&#039;Task B&#039;&#039; has control of that memory until &#039;&#039;task B&#039;&#039; returns the message to &#039;&#039;task A&#039;&#039; with the ReplyMsg() function.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s look at an example. Many applications use Intuition windows as a source for user input. Without getting into too much detail about Intuition, when an application opens a window, it can set up the window so Intuition will send messages about certain user input events. Intuition sends these messages to a message port created by Intuition for use with this window. When an application successfully opens a window, it receives a pointer to a Window structure, which contains a pointer to this message port (Window.UserPort). For this example, we&#039;ll assume the window has been set up so Intuition will send a message only if the user clicks the window&#039;s close gadget.&lt;br /&gt;
&lt;br /&gt;
When Intuition opens the window in this example, it creates a message port for the task that opened the Window. Because the most common message passing system uses signals, creating this message port involves using one of the example task&#039;s 32 signals. The OS uses this signal to signal the task when it receives a message at this message port. This allows the task to sleep while waiting for a &amp;quot;Close Window&amp;quot; event to arrive. Since this simple example is only waiting for activity at one message port, it can use the WaitPort() function. WaitPort() accepts a pointer to a message port and puts a task to sleep until a message arrives at that port.&lt;br /&gt;
&lt;br /&gt;
This simple example needs two variables, one to hold the address of the window and the other to hold the address of a message.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Message *mymsg; /*defined in &amp;lt;exec/ports.h&amp;gt; */&lt;br /&gt;
struct Window *mywin;  /* defined in &amp;lt;intuition/intuition.h&amp;gt; */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* at some point, this application would have successfully opened a */&lt;br /&gt;
/* window and stored a pointer to it in mywin.                      */&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    /* Here the application goes to sleep until the user clicks the window&#039;s close    */&lt;br /&gt;
    /* gadget.  This window was set up so that the only time Intuition will send a    */&lt;br /&gt;
    /* message to this window&#039;s port is if the user clicks the window&#039;s close gadget. */&lt;br /&gt;
&lt;br /&gt;
    IExec-&amp;gt;WaitPort(mywin-&amp;gt;UserPort);&lt;br /&gt;
    while (mymsg = IExec-&amp;gt;GetMsg(mywin-&amp;gt;UserPort))&lt;br /&gt;
        /* process message now or copy information from message */&lt;br /&gt;
        IExec-&amp;gt;ReplyMsg(mymsg);&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
/* Close window, clean up */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Exec function GetMsg() is used to extract messages from the message port. Since the memory for these messages belongs to Intuition, the example relinquishes each message as it finishes with them by calling ReplyMsg(). Notice that the example keeps on trying to get messages from the message port until mymsg is NULL. This is to make sure there are no messages left in the message port&#039;s message queue. It is possible for several messages to pile up in the message queue while the task is asleep, so the example has to make sure it replies to all of them. Note that the window should never be closed within this GetMsg() loop because the while statement is still accessing the window&#039;s UserPort.&lt;br /&gt;
&lt;br /&gt;
Note that each task with a Process structure (sometimes referred to as a process) has a special process message port, Process.pr_MsgPort. This message port is only for use by Workbench and the DOS library itself. &#039;&#039;No application should use this port for its own use!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
More detailed information about message ports can be found in [[Exec_Messages_and_Ports|Exec Messages and Ports]].&lt;br /&gt;
&lt;br /&gt;
==== Waiting on Message Ports and Signals at the Same Time ====&lt;br /&gt;
&lt;br /&gt;
Most applications need to wait for a variety of messages and signals from a variety of sources. For example, an application might be waiting for Window events and also timer.device messages. In this case, an application must Wait() on the combined signal bits of all signals it is interested in, including the signals for the message ports where any messages might arrive.&lt;br /&gt;
&lt;br /&gt;
The MsgPort structure, which is defined in &amp;amp;lt;exec/ports.h&amp;amp;gt;, is what Exec uses to keep track of a message port. The UserPort field from the example above points to one of these structures. In this structure is a field called mp_SigBit, which contains the &#039;&#039;number&#039;&#039; (not the actual bit mask) of the message port&#039;s signal bit. To Wait() on the signal of a message port, Wait() on a bit mask created by shifting 1U to the left mp_SigBit times (1U &amp;lt;&amp;lt; msgport-&amp;amp;gt;mp_SigBit). The resulting bit mask can be &#039;&#039;&#039;OR&#039;&#039;&#039;&#039;d with the bit masks for any other signals you wish to simultaneously wait on.&lt;br /&gt;
&lt;br /&gt;
== Libraries and Devices ==&lt;br /&gt;
&lt;br /&gt;
One of the design goals for the Amiga OS was to make the system dynamic enough so that the OS could be extended and updated without effecting existing applications. Another design goal was to make it easy for different applications to be able to share common pieces of code. The Amiga accomplished these goals through a system of libraries. An Amiga library consists of a collection of related functions which can be anywhere in system memory (RAM or ROM).&lt;br /&gt;
&lt;br /&gt;
Devices are very similar to libraries, except they usually control some sort of I/O device and contain some extra standard functions. Although this section does not really discuss devices directly, the material here applies to them. For more information on devices, see the [[Exec Device I/O]] section of this manual or the [[Devices|Devices]].&lt;br /&gt;
&lt;br /&gt;
An application accesses a library&#039;s functions through interfaces. Each library exports one or more interfaces. Before a task can use the functions of a particular interface, it must first acquire the library&#039;s base pointer by calling the Exec OpenLibrary() function and then obtain an interface pointer by calling GetInterface().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct Library *OpenLibrary( CONST_STRPTR libName, uint32 libVer );&lt;br /&gt;
struct Interface *GetInterface( struct Library *base, CONST_STRPTR ifaceName, uint32 ifaceVer, struct TagItem *tags);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where libName is a string naming the library and libVer is a version number for the library. The library base pointer is passed on to GetInterface() along with ifaceName which is the name of the interface and ifaceVer which is the version. An optional tag array completes the call.&lt;br /&gt;
&lt;br /&gt;
The libVer number reflects a revision of the system software. The chart below lists the specific Amiga OS release versions that system libraries versions correspond to:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| 30 || Kickstart 1.0 || This revision is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 31 || Kickstart 1.1 || This was an NTSC only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 32 || Kickstart 1.1 || This was a PAL only release and is obsolete.&lt;br /&gt;
|-&lt;br /&gt;
| 33 || Kickstart 1.2 ||This is the oldest revision of the OS still in use.&lt;br /&gt;
|-&lt;br /&gt;
| 34 || Kickstart 1.3 || This is almost the same as release 1.2 except it has an Autobooting expansion.library&lt;br /&gt;
|-&lt;br /&gt;
| 35 || || This is a special RAM-loaded version of the 1.3 revision, except that it knows about the &#039;&#039;A2024&#039;&#039; display modes. No application should need this library unless they need to open an &#039;&#039;A2024&#039;&#039; display mode under 1.3.&lt;br /&gt;
|-&lt;br /&gt;
| 36 || Kickstart 2.0 || This is the original Release 2 revision that was initially shipped on early &#039;&#039;Amiga 3000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 37 || Kickstart 2.04 || This is the general Release 2 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 38 || Kickstart 2.1 || This is an updated Release 2 revision for &#039;&#039;Amiga 600&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 39 || Kickstart 3.0 || This is the original Release 3 revision hat was initially shipped on &#039;&#039;Amiga 1200&#039;&#039; and &#039;&#039;Amiga 4000&#039;&#039; models.&lt;br /&gt;
|-&lt;br /&gt;
| 40 || Kickstart 3.1 || This is the general Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 44 || Kickstart 3.5 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 45 || Kickstart 3.9 || This is an update to Release 3 revision for all Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 50 || Kickstart 4.0 || This is the general Release 4 revision for PPC equipped Amiga models and the new &#039;&#039;AmigaOne&#039;&#039; range.&lt;br /&gt;
|-&lt;br /&gt;
| 51 || || &lt;br /&gt;
|-&lt;br /&gt;
| 52 || Kickstart 4.0 || This is an update to Release 4 revision to include support for PPC equipped classic Amiga models.&lt;br /&gt;
|-&lt;br /&gt;
| 53 || Kickstart 4.1 || This is an update to Release 4 revision for all supported models.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The OpenLibrary() function looks for a library with a name that matches libName and also with a version at least as high as libVersion. For example, to open version 50 or greater of the Intuition library:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
IntuitionBase = IExec-&amp;gt;OpenLibrary(&amp;quot;intuition.library&amp;quot;, 50);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example, if version 50 or greater of the Intuition library is not available, OpenLibrary() returns NULL. A version of zero in OpenLibrary() tells the OS to open any version of the library. New code should use a version of 50 or higher unless noted otherwise.&lt;br /&gt;
&lt;br /&gt;
When OpenLibrary() looks for a library, it first looks in memory. If the library is not in memory, OpenLibrary() will look for the library on disk. If libName is a library name with an absolute path (for example, &amp;quot;myapp:mylibs/mylib.library&amp;quot;), OpenLibrary() will follow that absolute path looking for the library. If libName is only a library name (&amp;quot;diskfont.library&amp;quot;), OpenLibrary() will look for the library in the directory that the LIBS: logical assign currently references.&lt;br /&gt;
&lt;br /&gt;
If OpenLibrary() finds the library on disk, it takes care of loading it and initializing it. As part of the initialization process, OpenLibrary() dynamically creates a vector table. There is a vector for each function in the library. The OS needs to create the vector table dynamically because the library functions can be anywhere in memory.&lt;br /&gt;
&lt;br /&gt;
After the library is loaded into memory and initialized, OpenLibrary() can actually &amp;quot;open&amp;quot; the library. It does this by calling the library&#039;s Open function vector. Every library has a standard vector set aside for an OPEN function so the library can set up any data or processes that it needs. Normally, a library&#039;s OPEN function increments its open count to keep track of how many tasks currently have the library opened.&lt;br /&gt;
&lt;br /&gt;
If any step of OpenLibrary() fails, it returns a NULL value. If OpenLibrary() is successful, it returns the address of the library base. The library base is the address of this library&#039;s Library structure (defined in &amp;quot;exec/libraries.h&amp;quot;). The Library structure immediately follows the vector table in memory.&lt;br /&gt;
&lt;br /&gt;
Once the library has been opened the application needs to obtain access to the interfaces with GetInterface() in order to call the functions available. Every task or process is required to call GetInterface() on the instance and should not share the interface pointer.&lt;br /&gt;
&lt;br /&gt;
After an application is finished with a library, it &#039;&#039;must&#039;&#039; close it by calling DropInterface() on each interface and finally CloseLibrary():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
VOID DropInterface(struct Interface *ifacePtr);&lt;br /&gt;
VOID CloseLibrary(struct Library *libPtr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where ifacePtr is a pointer to an interface and libPtr is a pointer to the library base returned when the library was opened with OpenLibrary().&lt;br /&gt;
&lt;br /&gt;
For more detailed information about libraries and interfaces see [[Exec_Libraries|Exec Libraries]].&lt;br /&gt;
&lt;br /&gt;
=== Calling a Library Function ===&lt;br /&gt;
&lt;br /&gt;
To call a function in an Amiga system library a pointer to an interface is required. For example to call the Intuition function DisplayBeep():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct IntuitionIFace *IIntuition = IExec-&amp;gt;GetInterface(base, &amp;quot;main&amp;quot; 1, NULL);&lt;br /&gt;
&lt;br /&gt;
IIntuition-&amp;gt;DisplayBeep();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Function parameters in C are pushed on the stack when a program calls a function. The order in which parameters are pushed is defined by the [http://en.wikipedia.org/wiki/Executable_and_Linkable_Format SysV ABI specification].&lt;br /&gt;
&lt;br /&gt;
For more detailed information about calling library functions see [[Exec_Libraries|Exec Libraries]].&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=BOOPSI_-_Object_Oriented_Intuition&amp;diff=3345</id>
		<title>BOOPSI - Object Oriented Intuition</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=BOOPSI_-_Object_Oriented_Intuition&amp;diff=3345"/>
		<updated>2012-07-06T05:18:11Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Function Reference */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{WIP}}&lt;br /&gt;
== BOOPSI - Object Oriented Intuition ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;BOOPSI&#039;&#039; is an acronym for &#039;&#039;Basic Object Oriented Programming System for Intuition&#039;&#039;. Using the Object Oriented Programming (OOP) model, BOOPSI represents certain Intuition entities, like Gadgets and Images, as objects.&lt;br /&gt;
&lt;br /&gt;
There are many advantages to using BOOPSI:&lt;br /&gt;
&lt;br /&gt;
* BOOPSI makes Intuition customizable and extensible. BOOPSI programmers can create new types of BOOPSI objects to suit the needs of their applications. These new types of objects are part of Intuition and can be made public so other applications can use them. Because applications can share the new types, application writers don&#039;t have to waste their time duplicating each other&#039;s efforts writing the same objects.&lt;br /&gt;
&lt;br /&gt;
* New types of BOOPSI objects can build on old types of BOOPSI objects, inheriting the old object&#039;s behavior. The result is that BOOPSI programmers don&#039;t have to waste their time building new objects from scratch, they simply add to the existing object.&lt;br /&gt;
&lt;br /&gt;
* OOP and BOOPSI apply the concept of interchangeable parts to Intuition programming. A BOOPSI programmer can combine different BOOPSI objects (like gadgets and images) to create an entire Graphical User Interface (GUI). The BOOPSI programmer doesn&#039;t have take the time to understand or implement the inner workings of these objects. The BOOPSI programmer only needs to know how to interact with BOOPSI objects and how to make them interact with each other.&lt;br /&gt;
&lt;br /&gt;
* BOOPSI objects have a consistent, command-driven interface. To the BOOPSI programmer, there is no difference between displaying a text, border, or bitmap-based BOOPSI image, even though they are rendered quite differently. Each image object accepts a single command to tell it to render itself.&lt;br /&gt;
&lt;br /&gt;
Before reading this chapter, you should already be familiar with several Amiga concepts. BOOPSI is built on top of Intuition and uses many of its structures. These include Intuition gadgets, images, and windows. BOOPSI also uses the tag concept to pass parameters. The &amp;quot;Utility Library&amp;quot; chapter of this manual discusses tags. [[Utility_Library|Utility Library]] also discusses callback Hooks, which are important to the later sections of this chapter.&lt;br /&gt;
&lt;br /&gt;
== OOP Overview ==&lt;br /&gt;
&lt;br /&gt;
Understanding BOOPSI requires an understanding of several of the concepts behind Object Oriented Programming. This section is a general overview of these concepts as they pertain to BOOPSI. Because BOOPSI is in part based on the concepts present in the OOP language &#039;&#039;Smalltalk&#039;&#039;, a reference book on &#039;&#039;Smalltalk&#039;&#039; may provide a deeper understanding of BOOPSI in general. Timothy Budd&#039;s book entitled &#039;&#039;A Little Smalltalk&#039;&#039; (Addison-Wesley Publishing ISBN 0-201-10698-1) is a good start.&lt;br /&gt;
&lt;br /&gt;
In the BOOPSI version of the Object Oriented Programming model, everything is an &#039;&#039;Object&#039;&#039;. For example, a proportional gadget named myprop is an object. Certain objects have similar characteristics and can be classified into groups called &#039;&#039;classes&#039;&#039;. As objects, Rover the dog, Bob the cat, and Sam the bird are all distinct objects but they all have something in common, they can all be classified as animals. As objects, myprop the proportional gadget, mystring the string gadget, and mybutton the button gadget all have something in common, they can all be classified as gadgets. A specific object is an &#039;&#039;instance&#039;&#039; of a particular class (&amp;quot;Rover&amp;quot; is an instance of class &amp;quot;animal&amp;quot;, &amp;quot;myslidergadget&amp;quot; is an instance of class &amp;quot;gadget&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
Notice that, although Rover, Bob, and Sam can all be classified as animals, each belongs to a subgroup of the animal class. &amp;quot;Rover&amp;quot; is an instance of class &amp;quot;dog&amp;quot;, &amp;quot;Bob&amp;quot; is an instance of class &amp;quot;cat&amp;quot;, and &amp;quot;Sam&amp;quot; is an instance of class &amp;quot;bird&amp;quot;. Because each of these animal types share common characteristics, each type makes up its own class. Because dog, cat, and bird are subclassifications of the animal class, they are known as &#039;&#039;subclasses&#039;&#039; of the animal class. Conversely, the animal class is the &#039;&#039;superclass&#039;&#039; of the dog, cat, and bird classes.&lt;br /&gt;
&lt;br /&gt;
Following the branches upward from class to superclass will bring you to a universal root category from which all objects are derived. The OOP language &#039;&#039;Smalltalk&#039;&#039; calls this class &amp;quot;Object&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Figure 12-1 Object Diagram&lt;br /&gt;
&lt;br /&gt;
Like &#039;&#039;Smalltalk&#039;&#039;, BOOPSI also has a universal root catagory, &#039;&#039;rootclass&#039;&#039;. Currently, Intuition defines three immediate subclasses of rootclass. The first, &#039;&#039;gadgetclass&#039;&#039;, is the class of BOOPSI gadgets. The second class, &#039;&#039;imageclass&#039;&#039;, makes up the class of BOOPSI images.&lt;br /&gt;
&lt;br /&gt;
Unlike gadgetclass and imageclass, the remaining subclass, &#039;&#039;icclass&#039;&#039;, does not correspond to an existing Intuition entity, it is a concept new to Intuition. Icclass, or &#039;&#039;interconnection class&#039;&#039;, allows one BOOPSI object to notify another BOOPSI object when a specific event occurs. For example, consider a BOOPSI proportional gadget and a BOOPSI image object that displays an integer value. An application can connect these two objects so that the prop gadget tells the image object the prop gadget&#039;s current value, which the image object displays. Every time the user slides the prop gadget, the prop gadget notifies the image of the change and the image updates its display to reflect the prop gadget&#039;s current integer value. Because these objects are talking to each other rather than the application, the updates happen automatically. The application doesn&#039;t have to talk to the two objects, it only has to connect them.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;sub&amp;gt;e&amp;lt;/sub&amp;gt;psf&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt;igureFig&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;2-2Simple BOOPSI Diagram&lt;br /&gt;
&lt;br /&gt;
An object&#039;s characteristics and behavior are determined by its class. Each class can define a set of &#039;&#039;attributes&#039;&#039; and a set of &#039;&#039;methods&#039;&#039; that apply to all objects of that class. An attribute is a variable characteristic of an object. For example, an attribute for the animal class could be the number of legs an animal object has. An example of a BOOPSI attribute is the X coordinate of a BOOPSI image object. The data that makes up the values of an object&#039;s attributes is collectively known as the &#039;&#039;instance data&#039;&#039; for that object.&lt;br /&gt;
&lt;br /&gt;
The behavior of an object depends upon the set of &#039;&#039;methods&#039;&#039; associated to it by its class. A method is basically a function that applies to objects of that class. An example of a BOOPSI method is the imageclass method IM_DRAW. This method tells a BOOPSI image to draw itself. All BOOPSI actions are carried out via methods.&lt;br /&gt;
&lt;br /&gt;
From the Object Diagram, two of the methods of the &amp;quot;animal&amp;quot; class could be &amp;quot;eat&amp;quot; and &amp;quot;sleep&amp;quot;. One of the methods of the &amp;quot;dog&amp;quot; class could be &amp;quot;bark&amp;quot;. Notice that instances of the &amp;quot;dog&amp;quot; class can do more than just bark, they can also eat and sleep. This is because a subclass &#039;&#039;inherits&#039;&#039; methods from its superclasses. If there were a subclass of dog called &amp;quot;attack dog&amp;quot;, all instances of that class would be able to bark, eat, and sleep, as well as &amp;quot;attack&amp;quot;. Due to inheritance, a subclass has all of the methods and all of the attributes of its superclass. For example, the IA_Height attribute is defined by imageclass. All instances of the subclasses of imageclass have their own IA_Height attribute, even though the subclasses do not explicitly define IA_Height. In turn, all instances of subclasses of the imageclass subclasses also inherit the IA_Height attribute. All classes on levels below a class will inherit its methods and attributes.&lt;br /&gt;
&lt;br /&gt;
When an application or a BOOPSI object wants another BOOPSI object to perform a method, it passes it a command in the form of a BOOPSI &#039;&#039;message&#039;&#039;. A BOOPSI message tells an object which method to perform. The message may also contain some parameters that the method requires.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Watch Out!&#039;&#039; The term &amp;quot;message&amp;quot; used in object oriented terminology can be little confusing to the Amiga programmer because the BOOPSI message has nothing to do with an Exec message.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
BOOPSI classes can be either &#039;&#039;public&#039;&#039; or &#039;&#039;private&#039;&#039;. Public classes have ASCII names associated with them and are accessible to all applications. Private classes have no ASCII name and normally can only be accessed by the application that created the private class.&lt;br /&gt;
&lt;br /&gt;
=== Using BOOPSI ===&lt;br /&gt;
&lt;br /&gt;
There are several levels on which a programmer can use BOOPSI. The most elementary level is to use Intuition functions to create and manipulate BOOPSI objects that are instances of existing, public classes.&lt;br /&gt;
&lt;br /&gt;
At present there is a hierarchy of 14 public classes built into Intuition:&lt;br /&gt;
&lt;br /&gt;
[[File:LibFig12-3.png]]&lt;br /&gt;
&lt;br /&gt;
Figure 12-3: Class Diagram&lt;br /&gt;
&lt;br /&gt;
==== BOOPSI and Tags ====&lt;br /&gt;
&lt;br /&gt;
BOOPSI uses tag lists to pass and manipulate its attributes. To BOOPSI, each TagItem (defined in &amp;amp;lt;utility/tagitem.h&amp;amp;gt;) in a tag list is an attribute/value pair. The TagItem.ti_Tag field contains an ID for the attribute and the ti_Data field holds the attribute&#039;s value.&lt;br /&gt;
&lt;br /&gt;
For example, the string gadget class defines an attribute called STRINGA_LongVal, which is the current integer value of the gadget. Certain gadgetclass objects have an attribute called GA_Image. Its value is not an integer, it is a pointer to an image.&lt;br /&gt;
&lt;br /&gt;
Note that these tag lists can also contain utility.library Global System control tags (like TAG_SKIP and TAG_DONE), which BOOPSI uses in processing its tag lists. Any application that ends up processing these lists should do so using the tag manipulation functions from utility.library. For more information on tags and utility.library, see [[Utility_Library|Utility Library]].&lt;br /&gt;
&lt;br /&gt;
==== Creating an Object ====&lt;br /&gt;
&lt;br /&gt;
The Intuition function NewObjectA() creates a BOOPSI object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mynewobject = APTR NewObjectA(Class *privclass, UBYTE *pubclass,&lt;br /&gt;
                              struct TagItem *myattrs);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The pointer that NewObjectA() returns is a pointer to a BOOPSI object. In general, BOOPSI objects are &amp;quot;black boxes&amp;quot;. This means the inner workings of BOOPSI objects are not visible to the application programmer, so the programmer does not know what goes on inside it. This really means the inner workings of these objects are none of your business. Unless otherwise documented, only use an object pointer as a handle to the object.&lt;br /&gt;
&lt;br /&gt;
To create an object, NewObjectA() needs to know what class the new object is an instance of. To create a public class object, pass a NULL pointer in privclass and an ASCII string in pubclass naming the object&#039;s public class. The privclass pointer is used to create a private class object, which is covered in the &amp;quot;Creating a BOOPSI Class&amp;quot; section later in this chapter.&lt;br /&gt;
&lt;br /&gt;
The myattrs tag list is a list of tag/value pairs, each of which contains an initial value for some object attribute. Most objects have a set of attributes associated with them, so each attribute has a tag name. For BOOPSI gadgets and images, the attributes include some of the values from the old Gadget and Image structures (position, size, etc.).&lt;br /&gt;
&lt;br /&gt;
Most applications use the stack-based version of NewObjectA(), NewObject(), to create objects. This allows an application to build the tag list of object attributes on the stack rather than having to allocate and initialize a tag list. A code sample from a program that creates a BOOPSI string gadget might look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mystringgadget = (struct Gadget *)NewObject(NULL, &amp;amp;quot;strgclass&amp;amp;quot;,&lt;br /&gt;
                                            GA_ID,           1L,&lt;br /&gt;
                                            GA_Left,         0L,&lt;br /&gt;
                                            GA_Top,          0L,&lt;br /&gt;
                                            STRINGA_LongVal, 100L,&lt;br /&gt;
                                            TAG_END);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If NewObject() is successful, it returns a pointer to a new BOOPSI gadget object. Otherwise, it returns NULL. The class &amp;quot;strgclass&amp;quot; is one of the public classes built into Intuition. It is a class of string gadgets.&lt;br /&gt;
&lt;br /&gt;
If you look at the diagram of the public classes built into Intuition, you&#039;ll see that strgclass is a subclass of gadgetclass. In the example above, the attribute tag IDs that start with &amp;quot;GA_&amp;quot; are defined by gadgetclass and not by strgclass. This is because strgclass inherits these attributes from its superclass, gadgetclass. The other attribute, STRINGA_LongVal, is defined by strgclass. It does two things. First, it tells the object that it is a special type of string gadget which only handles an integer value rather than a generic ASCII string. Second, it passes the object its initial integer value.&lt;br /&gt;
&lt;br /&gt;
==== Disposing of an Object ====&lt;br /&gt;
&lt;br /&gt;
When an application is done with an object it has to dispose of the object. To dispose of an object, use the Intuition function DisposeObject():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
VOID DisposeObject(APTR boopsiobject);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where boopsiobject is a pointer to the BOOPSI object to be disposed. Note that some classes allow applications to connect child objects to a parent object so that when the application deletes the parent object, it automatically disposes of all of its children. Be careful not to dispose of an object that has already been disposed.&lt;br /&gt;
&lt;br /&gt;
==== Setting an Existing Object&#039;s Attributes ====&lt;br /&gt;
&lt;br /&gt;
An object&#039;s attributes are not necessarily static. An application can ask an object to set certain object attributes using the SetAttrs() function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG SetAttrs(APTR myobject, Tag1, Value1, ...);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because BOOPSI gadgets require some extra information about their display, they use a special version of this function, SetGadgetAttrs():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG SetGadgetAttrs(struct Gadget *myobject, struct Window *w,&lt;br /&gt;
                     struct Requester *r, Tag1, Value1, ...);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here myobject is a pointer to the BOOPSI object, w points to the gadget&#039;s window, r points to the gadget&#039;s requester, and the tag/value pairs are the attributes and their new values. The return value of SetAttrs() and SetGadgetAttrs() is class specific. In general, if the attribute change causes a visual change to some object, the SetAttrs()/SetGadgetAttrs() function should return a non-zero value, otherwise, these functions should return zero (see the &#039;&#039;BOOPSI Class Reference&#039;&#039; for information on the return values for specific classes). The following is an example of how to set the current integer value and gadget ID of the gadget created in the NewObject() call above:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SetGadgetAttrs(mystringgadget, mywindow, NULL, STRINGA_LongVal,   75L,&lt;br /&gt;
                                               GA_ID,             2L,&lt;br /&gt;
                                               TAG_END));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This changes two of mystringgadget&#039;s attributes. It changes the gadget&#039;s current integer value to 75 and it changes the gadget&#039;s ID number to 2.&lt;br /&gt;
&lt;br /&gt;
Note that it is not OK to call SetGadgetAttrs() on a BOOPSI object that isn&#039;t a gadget, nor is it OK to call SetAttrs() on a BOOPSI gadget.&lt;br /&gt;
&lt;br /&gt;
Not all object attributes can be set with SetGadgetAttrs()/SetAttrs(). Some classes are set up so that applications cannot change certain attributes. For example, the imagery for the knob of a proportional gadget cannot be altered after the object has been created. Whether or not a specific attribute is &amp;quot;settable&amp;quot; is class dependent. For more information about the attributes of specific classes, see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual.&lt;br /&gt;
&lt;br /&gt;
==== Getting an Object&#039;s Attributes ====&lt;br /&gt;
&lt;br /&gt;
The Intuition function GetAttr() asks an object what the value of a specific attribute is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG GetAttr(ULONG attrID, APTR myobject, ULONG *mydata);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where attrID is the attribute&#039;s ID number, myobject is the object to get the attribute from, and mydata points to a data area that will hold the attribute value. This function returns a 0 if the object doesn&#039;t recognize the attribute, otherwise it returns some non-zero value, the meaning of which depends on the class. In most cases, GetAttr() returns a 1 when it is successful.&lt;br /&gt;
&lt;br /&gt;
Not all object attributes are obtainable using the GetAttr() function. Some classes are set up so that applications cannot query the state of certain attributes. For example, using the GA_Image attribute, an application can give a BOOPSI prop gadget (propgclass) an Image structure which the gadget uses as the imagery for its knob. This attribute is not &amp;quot;gettable&amp;quot; as there is no need for an application to have to ask the gadget for the structure that the application passed it in the first place. Whether or not a specific attribute is &amp;quot;gettable&amp;quot; is class dependent. For more information about the attributes of specific classes, see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual.&lt;br /&gt;
&lt;br /&gt;
==== What About the BOOPSI Messages and Methods? ====&lt;br /&gt;
&lt;br /&gt;
According to the &amp;quot;OOP Overview&amp;quot; section, for an object to perform a method, something has to pass it a BOOPSI message. The previous section discussed using Intuition functions to ask an object to do things like set and get attributes. The functions in the previous section seem to completely ignore all that material about methods and messages. What happened to the methods and messages?&lt;br /&gt;
&lt;br /&gt;
Nothing; these functions don&#039;t ignore the OOP constructs, they just shield the programmer from them. Each of these functions corresponds to a BOOPSI method:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| NewObject() || OM_NEW&lt;br /&gt;
|-&lt;br /&gt;
| DisposeObject() || OM_DISPOSE&lt;br /&gt;
|-&lt;br /&gt;
| SetAttrs()/SetGadgetAttrs() || OM_SET&lt;br /&gt;
|-&lt;br /&gt;
| GetAttr() || OM_GET&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These methods are defined on the rootclass level, so all BOOPSI classes inherit them. The Intuition functions that correspond to these methods take care of constructing and sending a BOOPSI message with the appropriate method ID and parameters.&lt;br /&gt;
&lt;br /&gt;
=== The Public Classes ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Intuition contains 14 public classes, all of which are descendants of the rootclass. There are three primary classes that descend directly from rootclass: &#039;&#039;imageclass&#039;&#039;, &#039;&#039;gadgetclass&#039;&#039;, and &#039;&#039;icclass&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== The Imageclass Subclasses ====&lt;br /&gt;
&lt;br /&gt;
Normally, an application does not create an imageclass object. Instead, it will use a subclass of imageclass. Currently, there are four subclasses: &#039;&#039;frameiclass&#039;&#039;, &#039;&#039;sysiclass&#039;&#039;, &#039;&#039;fillrectclass&#039;&#039;, and &#039;&#039;itexticlass&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
; frameiclass&lt;br /&gt;
: An embossed or recessed rectangular frame image, that renders itself using the proper DrawInfo pens. This class is intelligent enough to bound or center its contents.&lt;br /&gt;
&lt;br /&gt;
; sysiclass&lt;br /&gt;
: The class of system images. The class includes the images for the system and GadTools gadgets.&lt;br /&gt;
&lt;br /&gt;
; fillrectclass&lt;br /&gt;
: A class of rectangle images that have frame and patternfill support.&lt;br /&gt;
&lt;br /&gt;
; itextclass&lt;br /&gt;
: A specialized image class used for rendering text.&lt;br /&gt;
&lt;br /&gt;
For more information on these classes see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual. It describes all of the existing public classes, their methods, and their attributes.&lt;br /&gt;
&lt;br /&gt;
==== The Gadgetclass Subclasses ====&lt;br /&gt;
&lt;br /&gt;
Like imageclass, applications do not normally create objects of gadgetclass, but instead create objects of its subclasses. Currently, gadgetclass has four subclasses:&lt;br /&gt;
&lt;br /&gt;
; propgclass&lt;br /&gt;
: An easy to implement, horizontal or vertical proportional gadget.&lt;br /&gt;
&lt;br /&gt;
; strgclass&lt;br /&gt;
: A string gadget.&lt;br /&gt;
&lt;br /&gt;
; groupclass&lt;br /&gt;
: A special gadget class that creates one composite gadget out of several others.&lt;br /&gt;
&lt;br /&gt;
; buttongclass&lt;br /&gt;
: A button gadget that keeps sending button presses while the user holds it down.&lt;br /&gt;
&lt;br /&gt;
buttongclass has a subclass of its own:&lt;br /&gt;
&lt;br /&gt;
; frbuttonclass&lt;br /&gt;
: A buttongclass gadget that outlines its imagery with a frame.&lt;br /&gt;
&lt;br /&gt;
For specific information on these classes, see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual.&lt;br /&gt;
&lt;br /&gt;
=== Making Gadget Objects Talk to Each Other ===&lt;br /&gt;
&lt;br /&gt;
One use for a proportional gadget is to let the user change some integer value, like the red, green, and blue components of a color. This type of prop gadget is commonly accompanied by an integer string gadget, enabling the user to adjust one integer value by either typing the value into the string gadget or by scrolling the prop gadget. Because these two gadgets reflect the value of the same integer, when the user adjusts the state of one of the gadgets (and thus changing the integer value), the other gadget should automatically update to reflect the new integer value.&lt;br /&gt;
&lt;br /&gt;
When the user manipulates a conventional gadget, the gadget sends messages to an IDCMP port to indicate the state change (for information on IDCMP, see [[Intuition_Input_and_Output_Methods|Intuition Input and Output Methods]]). To connect the string and prop gadgets from the previous paragraph, an application would have to listen for the IDCMP messages from two different gadgets, interpret the IDCMP message&#039;s meaning, and manually update the gadgets accordingly. Essentially, the application is responsible for &amp;quot;gluing&amp;quot; the gadgets together. This unnecessarily complicates an application, especially when that application already has to listen for and interpret many other events.&lt;br /&gt;
&lt;br /&gt;
BOOPSI gadgets simplify this. By setting the appropriate attributes, an application can ask a BOOPSI gadget to tell some other object when its state changes. One of the attributes defined by gadgetclass is ICA_TARGET (defined in &amp;amp;lt;intuition/icclass.h&amp;amp;gt;). The ICA_TARGET attribute points to another BOOPSI object. When certain attributes in a BOOPSI gadget change (like the integer value of a prop gadget), that gadget looks to see if it has an ICA_TARGET. If it does, it sends the target a message telling it to perform an OM_UPDATE method.&lt;br /&gt;
&lt;br /&gt;
The OM_UPDATE method is defined by rootclass. This is basically a special type of OM_SET method that is used specifically to tell a BOOPSI object that another BOOPSI object&#039;s state changed. Only BOOPSI objects send OM_UPDATE messages. Note that standard classes of BOOPSI gadgets only send out OM_UPDATE messages as a result of the user changing the state of the gadget (scrolling the prop gadget, typing a new number into an integer gadget, etc.). These gadgets do not send out OM_UPDATE messages when they receive OM_SET or OM_UPDATE messages.&lt;br /&gt;
&lt;br /&gt;
A BOOPSI propgclass object has only one attribute that triggers it to send an OM_UPDATE request: PGA_Top. This attribute contains the integer value of the prop gadget. Every time the user moves a prop gadget, the PGA_Top attribute changes. If the prop gadget has an ICA_TARGET, the prop gadget will tell the target object that the PGA_Top value has changed.&lt;br /&gt;
&lt;br /&gt;
A BOOPSI integer string gadget (a strgclass object) also has only one attribute that triggers it to send an OM_UPDATE request: STRINGA_LongVal. This value contains the integer value of the integer string gadget. Like the prop gadget, if the integer string gadget has an ICA_TARGET, when the user changes the gadget&#039;s integer value (STRINGA_LongVal), the string gadget will tell the target object that the STRINGA_LongVal value has changed.&lt;br /&gt;
&lt;br /&gt;
When a BOOPSI gadget sends an OM_UPDATE message, it passes the ID of the attribute that changed plus that attribute&#039;s new value. For example, if the user typed a 25 into a BOOPSI integer string gadget, that gadget would send an OM_UPDATE message to its ICA_TARGET saying in essence, &amp;quot;Hey, STRINGA_LongVal is 25&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
If this string gadget&#039;s ICA_TARGET is a propgclass object, the propgclass object will become confused because it has no idea what a STRINGA_LongVal attribute is. The string gadget needs to &#039;&#039;map&#039;&#039; its STRINGA_LongVal ID to the PGA_Top ID. This is what the ICA_MAP attribute is for.&lt;br /&gt;
&lt;br /&gt;
The ICA_MAP attribute is defined by gadgetclass (it is also defined for icclass-more on that later). It accepts a tag list of attribute mappings. When a gadget sends out an OM_UPDATE message, it uses this map to translate a specific attribute ID to another attribute ID, without changing the value of the attribute. Each TagItem in the ICA_MAP makes up a single attribute mapping. The TagItem.ti_Tag of the mapping is the ID of an attribute to translate. The gadget translates that attribute ID to the attribute ID in TagItem.ti_Data. For example, an ICA_MAP that maps a string gadget&#039;s STRINGA_LongVal attribute to a prop gadget&#039;s PGA_Top attribute looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct TagItem slidertostring[] = {&lt;br /&gt;
    {PGA_Top, STRINGA_LongVal},&lt;br /&gt;
    {TAG_END, }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that it is OK to have an ICA_TARGET without having an ICA_MAP. In cases where a gadget and its ICA_TARGET have a set of attributes in common, it would be unnecessary to use an ICA_MAP to match a gadget&#039;s attributes, as they already match.&lt;br /&gt;
&lt;br /&gt;
The following example, Talk2boopsi.c, creates a prop gadget and an integer string gadget which update each other without the example program having to process any messages from them.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* Talk2boopsi.c - Execute me to compile me with SAS/C 5.10b&lt;br /&gt;
LC -b1 -cfistq -v -y -j73 Talk2boopsi.c&lt;br /&gt;
Blink FROM LIB:c.o,Talk2boopsi.o TO Talk2boopsi LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit ;*/&lt;br /&gt;
/* This example creates a BOOPSI prop gadget and integer string gadget, connecting them so they */&lt;br /&gt;
/* update each other when the user changes their value.  The example program only initializes   */&lt;br /&gt;
/* the gadgets and puts them on the window; it doesn&#039;t have to interact with them to make them  */&lt;br /&gt;
/* talk to each other.                                                                          */&lt;br /&gt;
&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/tagitem.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/intuition.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt; /* contains IDs for gadget attributes */&lt;br /&gt;
#include &amp;amp;lt;intuition/icclass.h&amp;amp;gt;     /* contains ICA_MAP, ICA_TARGET       */&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/intuition_protos.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE                     /* Disable SAS/C CTRL/C handling    */&lt;br /&gt;
int CXBRK(void) { return(0); }&lt;br /&gt;
int chkabort(void) { return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
UBYTE *vers = &amp;amp;quot;\0$VER: Talk2boopsi 37.1&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
struct Library *IntuitionBase;&lt;br /&gt;
struct Window *w;&lt;br /&gt;
struct IntuiMessage *msg;&lt;br /&gt;
struct Gadget *prop, *integer;&lt;br /&gt;
&lt;br /&gt;
/* The attribute mapping lists */&lt;br /&gt;
struct TagItem prop2intmap[] =    /* This tells the prop gadget to */&lt;br /&gt;
{                                 /* map its PGA_Top attribute to  */&lt;br /&gt;
    {PGA_Top,   STRINGA_LongVal}, /* STRINGA_LongVal when it       */&lt;br /&gt;
    {TAG_END,}                    /* issues an update about the    */&lt;br /&gt;
};                                /* change to its PGA_Top value.  */&lt;br /&gt;
&lt;br /&gt;
struct TagItem int2propmap[] =    /* This tells the string gadget */&lt;br /&gt;
{                                 /* to map its STRINGA_LongVal   */&lt;br /&gt;
    {STRINGA_LongVal,   PGA_Top}, /* attribute to PGA_Top when it */&lt;br /&gt;
    {TAG_END,}                    /* issues an update.            */&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
#define PROPGADGET_ID       1L&lt;br /&gt;
#define INTGADGET_ID        2L&lt;br /&gt;
#define PROPGADGETWIDTH     10L&lt;br /&gt;
#define PROPGADGETHEIGHT    80L&lt;br /&gt;
#define INTGADGETHEIGHT     18L&lt;br /&gt;
#define VISIBLE             10L&lt;br /&gt;
#define TOTAL               100L&lt;br /&gt;
#define INITIALVAL          25L&lt;br /&gt;
#define MINWINDOWWIDTH      80&lt;br /&gt;
#define MINWINDOWHEIGHT     (PROPGADGETHEIGHT + 70)&lt;br /&gt;
#define MAXCHARS            3L&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
    BOOL done = FALSE;&lt;br /&gt;
&lt;br /&gt;
    if (IntuitionBase = OpenLibrary(&amp;amp;quot;intuition.library&amp;amp;quot;, 37L))&lt;br /&gt;
    {                                /* Open the window--notice that the window&#039;s IDCMP port     */&lt;br /&gt;
                                     /* does not listen for GADGETUP messages.                   */&lt;br /&gt;
        if (w = OpenWindowTags(NULL,&lt;br /&gt;
                 WA_Flags,       WFLG_DEPTHGADGET | WFLG_DRAGBAR |&lt;br /&gt;
                                     WFLG_CLOSEGADGET | WFLG_SIZEGADGET,&lt;br /&gt;
                 WA_IDCMP,       IDCMP_CLOSEWINDOW,&lt;br /&gt;
                 WA_MinWidth,    MINWINDOWWIDTH,&lt;br /&gt;
                 WA_MinHeight,   MINWINDOWHEIGHT,&lt;br /&gt;
                 TAG_END))&lt;br /&gt;
        {                                 /* Create a new propgclass object */&lt;br /&gt;
            if (prop = (struct Gadget *)NewObject(NULL, &amp;amp;quot;propgclass&amp;amp;quot;,&lt;br /&gt;
                GA_ID,     PROPGADGET_ID,        /* These are defined by gadgetclass and         */&lt;br /&gt;
                GA_Top,    (w-&amp;amp;gt;BorderTop) + 5L,  /* correspond to similarly named fields in      */&lt;br /&gt;
                GA_Left,   (w-&amp;amp;gt;BorderLeft) + 5L, /* the Gadget structure.                        */&lt;br /&gt;
                GA_Width,  PROPGADGETWIDTH,&lt;br /&gt;
                GA_Height, PROPGADGETHEIGHT,&lt;br /&gt;
&lt;br /&gt;
                ICA_MAP,      prop2intmap, /* The prop gadget&#039;s attribute map */&lt;br /&gt;
&lt;br /&gt;
             /* The rest of this gadget&#039;s attributes are defined by propgclass. */&lt;br /&gt;
                PGA_Total,     TOTAL,          /* This is the integer range of the prop gadget.  */&lt;br /&gt;
                PGA_Top,       INITIALVAL,     /* The initial integer value of the prop gadget.  */&lt;br /&gt;
&lt;br /&gt;
                PGA_Visible,   VISIBLE, /* This determines how much of the prop gadget area is   */&lt;br /&gt;
                                        /* covered by the prop gadget&#039;s knob, or how much of     */&lt;br /&gt;
                                        /* the gadget&#039;s TOTAL range is taken up by the prop      */&lt;br /&gt;
                                        /* gadget&#039;s knob.                                        */&lt;br /&gt;
&lt;br /&gt;
                PGA_NewLook,   TRUE,    /* Use new-look prop gadget imagery */&lt;br /&gt;
                TAG_END))&lt;br /&gt;
            {                           /* create the integer string gadget.                     */&lt;br /&gt;
                if (integer = (struct Gadget *)NewObject(NULL, &amp;amp;quot;strgclass&amp;amp;quot;,&lt;br /&gt;
                    GA_ID,      INTGADGET_ID,         /* Parameters for the Gadget structure     */&lt;br /&gt;
                    GA_Top,     (w-&amp;amp;gt;BorderTop) + 5L,&lt;br /&gt;
                    GA_Left,    (w-&amp;amp;gt;BorderLeft) + PROPGADGETWIDTH + 10L,&lt;br /&gt;
                    GA_Width,   MINWINDOWWIDTH -&lt;br /&gt;
                                  (w-&amp;amp;gt;BorderLeft + w-&amp;amp;gt;BorderRight +&lt;br /&gt;
                                  PROPGADGETWIDTH + 15L),&lt;br /&gt;
                    GA_Height,  INTGADGETHEIGHT,&lt;br /&gt;
&lt;br /&gt;
                    ICA_MAP,    int2propmap,           /* The attribute map */&lt;br /&gt;
                    ICA_TARGET, prop,                  /* plus the target.  */&lt;br /&gt;
&lt;br /&gt;
                            /* Th GA_Previous attribute is defined by gadgetclass and is used to */&lt;br /&gt;
                            /* wedge a new gadget into a list of gadget&#039;s linked by their        */&lt;br /&gt;
                            /* Gadget.NextGadget field.  When NewObject() creates this gadget,   */&lt;br /&gt;
                            /* it inserts the new gadget into this list behind the GA_Previous   */&lt;br /&gt;
                            /* gadget. This attribute is a pointer to the previous gadget        */&lt;br /&gt;
                            /* (struct Gadget *).  This attribute cannot be used to link new     */&lt;br /&gt;
                            /* gadgetsinto the gadget list of an open window or requester,       */&lt;br /&gt;
                    GA_Previous, prop,  /* use AddGList() instead.                               */&lt;br /&gt;
&lt;br /&gt;
                    STRINGA_LongVal,  INITIALVAL, /* These attributes are defined by strgclass.  */&lt;br /&gt;
                    STRINGA_MaxChars, MAXCHARS,   /* The first contains the value of the         */&lt;br /&gt;
                    TAG_END))                     /* integer string gadget. The second is the    */&lt;br /&gt;
                                                  /* maximum number of characters the user is    */&lt;br /&gt;
                                                  /* allowed to type into the gadget.            */&lt;br /&gt;
                 {&lt;br /&gt;
                    SetGadgetAttrs(prop, w, NULL, /* Because the integer string gadget did not   */&lt;br /&gt;
                        ICA_TARGET, integer,      /* exist when this example created the prop    */&lt;br /&gt;
                        TAG_END);                 /* gadget, it had to wait to set the           */&lt;br /&gt;
                                                  /* ICA_Target of the prop gadget.              */&lt;br /&gt;
&lt;br /&gt;
                    AddGList(w, prop, -1, -1, NULL);  /* Add the gadgets to the                  */&lt;br /&gt;
                    RefreshGList(prop, w, NULL, -1);  /* window and display them.                */&lt;br /&gt;
&lt;br /&gt;
                    while (done == FALSE)     /* Wait for the user to click */&lt;br /&gt;
                    {                         /* the window close gadget.   */&lt;br /&gt;
                        WaitPort((struct MsgPort *)w-&amp;amp;gt;UserPort);&lt;br /&gt;
                        while (msg = (struct IntuiMessage *)&lt;br /&gt;
                           GetMsg((struct MsgPort *)w-&amp;amp;gt;UserPort))&lt;br /&gt;
                        {&lt;br /&gt;
                            if (msg-&amp;amp;gt;Class == IDCMP_CLOSEWINDOW)&lt;br /&gt;
                                done = TRUE;&lt;br /&gt;
                            ReplyMsg(msg);&lt;br /&gt;
                        }&lt;br /&gt;
                    }&lt;br /&gt;
                    RemoveGList(w, prop, -1);&lt;br /&gt;
                    DisposeObject(integer);&lt;br /&gt;
                 }&lt;br /&gt;
                DisposeObject(prop);&lt;br /&gt;
            }&lt;br /&gt;
            CloseWindow(w);&lt;br /&gt;
        }&lt;br /&gt;
        CloseLibrary(IntuitionBase);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Making Gadgets Talk to an Application ===&lt;br /&gt;
&lt;br /&gt;
There are two questions that the example above brings to mind. The first is, &amp;quot;What happens if the user types a value into the string gadget that is beyond the bounds of the prop gadget?&amp;quot; The answer is simple: very little. The prop gadget is smart enough to make sure its integer value does not go beyond the bounds of its display. In the example, the prop gadget can only have values from 0 to 90. If the user tries to type a value greater than 90, the prop gadget will set itself to its maximum of 90. Because the integer string gadget doesn&#039;t have any bounds checking built into it, the example needs to find an alternative way to check the bounds.&lt;br /&gt;
&lt;br /&gt;
The other question is, &amp;quot;How does talk2boopsi.c know the current value of the gadgets?&amp;quot; That answer is simple too: it doesn&#039;t. The example doesn&#039;t ask the gadgets what their current values are (which it would do using GetAttr()) and the example doesn&#039;t pay attention to gadget events at the window&#039;s IDCMP port, so it isn&#039;t going to hear about them.&lt;br /&gt;
&lt;br /&gt;
One easy way to hear about changes to the gadget events is to listen for a &amp;quot;release verify&amp;quot;. Conventional Intuition gadgets can trigger a release verify IDCMP event when the user finishes manipulating the gadget. BOOPSI gadgets can do this, too, while continuing to update each other.&lt;br /&gt;
&lt;br /&gt;
To make Talk2boopsi.c do this would require only a few changes. First, the window&#039;s IDCMP port has to be set up to listen for IDCMP_GADGETUP events. Next, the example needs to set the gadget&#039;s GLFG_RELVERIFY flags. It can do this by setting the gadgetclass GA_RelVerify attribute to TRUE for both gadgets. That&#039;s enough to trigger the release verify message, so all Talk2boopsi.c needs to do is account for the new type of IDCMP message, IDCMP_GADGETUP. When Talk2boopsi.c gets a release verify message, it can use GetAttr() to ask the integer gadget its value. If this value is out of range, it should explicitly set the value of the integer gadget to a more suitable value using SetGadgetAttrs().&lt;br /&gt;
&lt;br /&gt;
Using the GLFG_RELVERIFY scheme above, an application will only hear about changes to the gadgets after the user is finished changing them. The application does not hear all of the interim updates that, for example, a prop gadget generates. This is useful if an application only needs to hear the final value and not the interim update.&lt;br /&gt;
&lt;br /&gt;
It is also possible to make the IDCMP port of a BOOPSI gadget&#039;s window the ICA_TARGET of the gadget. There is a special value for ICA_TARGET called ICTARGET_IDCMP (defined in &amp;amp;lt;intuition/icclass.h&amp;amp;gt;). This tells the gadget to send an IDCMP_IDCMPUPDATE class IntuiMessage to its window‚Äôs IDCMP port. Of course, the window has to be set up to listen for IDCMP_IDCMPUPDATE IntuiMessages. The BOOPSI gadget passes an address in the IntuiMessage.IAddress field. It points to an attribute tag list containing the attribute (and its new value) that triggered the IDCMP_IDCMPUPDATE message. An application can use the utility.library tag functions to access the gadget&#039;s attributes in this list. Using this scheme, an application will hear all of the interim gadget updates. If the application is using a gadget that generates a lot of interim OM_UPDATE messages (like a prop gadget), the application should be prepared to handle a lot of messages.&lt;br /&gt;
&lt;br /&gt;
Using this IDCMP_IDCMPUPDATE scheme, if the gadget uses an ICA_MAP to map the attribute to a special dummy attribute ICSPECIAL_CODE (defined in &amp;amp;lt;intuition/icclass.h&amp;amp;gt;), the IntuiMessage.Code field will contain the value of the attribute. Because the attribute&#039;s value is a 32-bit quantity and the IntuiMessage.Code field is only 16 bits wide, only the least significant 16 bits of the attribute will appear in the IntuiMessage.Code field, so it can&#039;t hold a 32-bit quantity, like a pointer. Applications should only use the lower 16 bits of the attribute value.&lt;br /&gt;
&lt;br /&gt;
=== The Interconnection Classes ===&lt;br /&gt;
&lt;br /&gt;
The IDCMP_IDCMPUPDATE scheme presents a problem to an application that wants to make gadgets talk to each other and talk to the application. BOOPSI gadgets only have one ICA_TARGET. One BOOPSI gadget can talk to either another BOOPSI object or its window&#039;s IDCMP port, but not both. Using this scheme alone would force the application to update the integer value of the gadgets, which is what we are trying to avoid in the first place.&lt;br /&gt;
&lt;br /&gt;
One of the standard BOOPSI classes, icclass, is a class of information forwarders. An icclass object receives OM_UPDATE messages from one object and passes those messages on to its own ICA_TARGET. If it needs to map any incoming attributes, it can use its own ICA_MAP to do so.&lt;br /&gt;
&lt;br /&gt;
Icclass has a subclass called &#039;&#039;modelclass&#039;&#039;. Using a modelclass object, an application can chain a series of these objects together to set up a &amp;quot;broadcast list&amp;quot; of icclass objects. The modelclass object is similar to the icclass object in that it has its own ICA_TARGET and ICA_MAP. It differs in that an application can use the modelclass OM_ADDMEMBER method to add icclass objects to the modelclass object&#039;s broadcast list.&lt;br /&gt;
&lt;br /&gt;
The OM_ADDMEMBER method is defined by rootclass. It adds one BOOPSI object to the personal list of another BOOPSI object. It is up to the BOOPSI object&#039;s class to determine the purpose of the objects in the list. Unlike the other methods mentioned so far in this chapter, OM_ADDMEMBER does not have an Intuition function equivalent. To pass an OM_ADDMEMBER message to an object use the function DoMethodA(), or its stack-based equivalent, DoMethod():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG DoMethodA(Object *myobject, Msg boopsimessage);&lt;br /&gt;
ULONG DoMethod(Object *myobject, ULONG methodID, ...);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The return value is class-dependent. The first argument to both of these functions points to the object that will receive the BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
For DoMethodA(), boopsimessage is the actual BOOPSI message. The layout of it depends on the method. Every method&#039;s message starts off with an Msg (from &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
typedef struct {&lt;br /&gt;
    ULONG MethodID; /* Method-specific data may follow this field */&lt;br /&gt;
} *Msg;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The message that the OM_ADDMEMBER method uses looks like this (from &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct opMember {&lt;br /&gt;
    ULONG    MethodID;&lt;br /&gt;
    Object   *opam_Object;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where MethodID is OM_ADDMEMBER and opam_Object points to the object to add to myobject&#039;s list.&lt;br /&gt;
&lt;br /&gt;
DoMethod() uses the stack to build a message. To use DoMethod(), just pass the elements of the method&#039;s message structure as arguments to DoMethod() in the order that they appear in the structure. For example, to ask the BOOPSI object myobject to add the object addobject to its personal list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DoMethod(myobject, OM_ADDMEMBER, addobject);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To rearrange Talk2boopsi.c so that it uses a modelclass object (also known as a model):&lt;br /&gt;
&lt;br /&gt;
* Create the integer and prop gadget.&lt;br /&gt;
* Create the model.&lt;br /&gt;
* Create two icclass objects, one called int2prop and the other called prop2int.&lt;br /&gt;
* Make the model the ICA_TARGET of both the integer gadget and the prop gadget. The gadgets do not need an ICA_MAP.&lt;br /&gt;
* Using DoMethod() to call OM_ADDMEMBER, add the icclass objects to the model&#039;s personal list.&lt;br /&gt;
* Make the prop gadget the ICA_TARGET of int2prop. Make the integer gadget the ICA_TARGET of prop2int.&lt;br /&gt;
* Create an ICA_MAP map list for int2prop that maps STRINGA_LongVal to PGA_Top. Create an ICA_MAP map list for prop2int that maps PGA_Top to STRINGA_LongVal. Make the ICA_TARGET of the model ICTARGET_IDCMP.&lt;br /&gt;
&lt;br /&gt;
Diagrammatically, the new Talk2boopsi.c should look something like this:&lt;br /&gt;
&lt;br /&gt;
Figure 12-4 ICC Diagram&lt;br /&gt;
&lt;br /&gt;
When either of these gadgets has some interim state change (caused by the user manipulating the gadgets), it sends an OM_UPDATE message to its ICA_TARGET, which in this case is the modelclass object. When this model gets the message, it does two things. It sends an IDCMP_IDCMPUPDATE to the IDCMP port of the gadget&#039;s window and it also sends OM_UPDATE messages to all of the objects in its personal list. When int2prop gets an OM_UPDATE message, it forwards that message to its ICA_TARGET, the prop gadget. Similarly, when prop2int gets an OM_UPDATE message, it forwards that message to its ICA_TARGET, the integer gadget.&lt;br /&gt;
&lt;br /&gt;
Although in this case it isn&#039;t a problem, icclass and modelclass objects contain loop inhibition capabilities. If an icclass object (or modelclass object) receives an OM_UPDATE message, it forwards the message to its target. If somehow that forwarded message gets forwarded (or broadcast) back to the icclass object, the icclass object ignores the message. This prevents the possibility of an infinite OM_UPDATE loop.&lt;br /&gt;
&lt;br /&gt;
== Creating a BOOPSI Class ==&lt;br /&gt;
&lt;br /&gt;
So far this chapter has only hinted at what is possible with BOOPSI. Its power lies in its extensibility. BOOPSI grants the application programmer the power to add custom features to existing classes. If an existing class comes close to your needs, you can build on that class so it does exactly what you want. If you want a class that is unlike an existing class, you can create it.&lt;br /&gt;
&lt;br /&gt;
The heart of a BOOPSI class is its method &#039;&#039;Dispatcher&#039;&#039; function. According to the OOP metaphor, when an application wants a BOOPSI object to perform a method, it sends the object a message. In reality, that object is only a data structure, so it does not have the power to do anything. When an object receives a BOOPSI message, a BOOPSI message structure is passed to the dispatcher of that object&#039;s class. The dispatcher examines the message and figures out what to do about it.&lt;br /&gt;
&lt;br /&gt;
For example, when an application calls SetGadgetAttrs() on an integer gadget:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SetGadgetAttrs(myintegergadget, mywindow, NULL,&lt;br /&gt;
               STRINGA_LongVal, 75L,&lt;br /&gt;
               GA_ID,           2L,&lt;br /&gt;
               TAG_END));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the SetGadgetAttrs() function calls the strgclass dispatcher. A BOOPSI dispatcher receives three arguments: a pointer to the dispatcher&#039;s Class (defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;), a pointer to the object that is going to perform the method, and a pointer to the BOOPSI message. In this case, the SetGadgetAttrs() function builds an OM_SET message, finds the strgclass dispatcher, and &amp;quot;sends&amp;quot; the dispatcher the OM_SET message. SetGadgetAttrs() can find the dispatcher because an object contains a reference to its dispatcher.&lt;br /&gt;
&lt;br /&gt;
When the dispatcher function &amp;quot;gets&amp;quot; the message, it examines the message to find out its corresponding method. In this case, the dispatcher recognizes the message as an OM_SET message and proceeds to set myintegergadget&#039;s attributes.&lt;br /&gt;
&lt;br /&gt;
An OM_SET message looks like this (defined in &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct opSet {&lt;br /&gt;
    ULONG MethodID;                   /* This will be set to OM_SET      */&lt;br /&gt;
    struct TagItem    *ops_AttrList;  /* A tag list containing the       */&lt;br /&gt;
                                      /*     attribute/value pairs of    */&lt;br /&gt;
                                      /*     the attributes to set.      */&lt;br /&gt;
    struct GadgetInfo *ops_GInfo;     /* Special information for gadgets */&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The OM_SET message contains a pointer to a tag list in ops_AttrList that looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{STRINGA_LongVal, 75L},&lt;br /&gt;
{GA_ID, 2L},&lt;br /&gt;
{TAG_END,}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The strgclass dispatcher scans through this tag list and recognizes the STRINGA_LongVal attribute. The dispatcher sets myintegergadget&#039;s internal STRINGA_LongVal value to the corresponding value (75L) from the attribute/value pair.&lt;br /&gt;
&lt;br /&gt;
The strgclass dispatcher continues to scan through the tag list. When it finds GA_ID, it does not process it like STRINGA_LongVal. The strgclass dispatcher&#039;s OM_SET method does not recognize the GA_ID attribute because strgclass &#039;&#039;inherited&#039;&#039; the GA_ID attribute from gadgetclass. To handle setting the GA_ID attribute, the strgclass dispatcher passes on the OM_SET message to its superclass&#039;s dispatcher. The strgclass dispatcher passes control to the gadgetclass dispatcher, which knows about the GA_ID attribute.&lt;br /&gt;
&lt;br /&gt;
=== Building on Existing Public Classes ===&lt;br /&gt;
&lt;br /&gt;
A program can create its own subclasses which build on the features of existing classes. For example, a program could create a subclass of modelclass named rkmmodelclass. Rkmmodelclass builds on modelclass by adding a new attribute called RKMMOD_CurrVal. This purpose of this attribute is simply to hold an integer value.&lt;br /&gt;
&lt;br /&gt;
Because this new attribute is built into an rkmmodel object, the object could be implemented so that it exercises a certain amount of control over that value. For example, rkmmodelclass could be implemented so an rkmmodel performs bounds checking on its internal value. When an application asks an rkmmodel to set its internal RKMMOD_CurrVal, the rkmmodel makes sure the new value is not beyond a maximum value. If the new value is beyond the maximum, it sets its current value to the maximum. After the rkmmodelclass object has set its internal RKMMOD_CurrVal, it can broadcast the change on to objects in its broadcast list.&lt;br /&gt;
&lt;br /&gt;
The dispatcher for rkmmodelclass does not have to do a lot of work because it inherits most of its behavior from its superclasses. The rkmmodelclass has to take care of setting aside memory for the RKMMOD_CurrVal attribute and processing any OM_SET requests to set the RKMMOD_CurrVal attribute. For any other attributes or methods, the rkmmodelclass dispatcher passes on processing to its superclass, modelclass.&lt;br /&gt;
&lt;br /&gt;
==== Building Rkmmodelclass ====&lt;br /&gt;
&lt;br /&gt;
So far, the theoretical class rkmmodelclass has just one attribute, RKMMOD_CurrVal. A couple of extra attributes can make it more useful. Because the rkmmodel object maintains an upper limit on its RKMMOD_CurrVal integer value, it would be useful if that upper limit was variable. Using a new attribute, RKMMOD_Limit, an application can tell a rkmmodel what its upper limit is. The rkmmodel will enforce the limit internally, so the application doesn‚Äôt have to worry about it.&lt;br /&gt;
&lt;br /&gt;
Another useful addition is a pulse increment and decrement for RKMMOD_CurrVal. Whenever the model receives an increment or decrement command, it increments or decrements its internal value. To make the example class simple, rkmmodelclass implements incrementing and decrementing by creating &amp;quot;dummy&amp;quot; attributes called RKMMOD_Up and RKMMOD_Down. When an rkmmodel receives an OM_SET message for one of these attributes, it increments or decrements RKMMOD_CurrVal. An rkmmodelclass object does not care what the value of the RKMMOD_Up and RKMMOD_Down attributes are, it only cares that it received an OM_UPDATE about it.&lt;br /&gt;
&lt;br /&gt;
There are two pieces of data that make up this new class&#039;s instance data: the rkmmodel&#039;s current value (RKMMOD_CurrVal) and the upper limit of the rkmmodel (RKMMOD_Limit). The example class consolidates them into one structure:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct RKMModData {&lt;br /&gt;
    ULONG currval;&lt;br /&gt;
    ULONG vallimit;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Writing the Dispatcher ===&lt;br /&gt;
&lt;br /&gt;
The C prototype for a BOOPSI dispatcher looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG dispatchRKMModel(Class *cl, Object *recvobject, Msg msg);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where cl points to the Class (defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;) of the dispatcher, recvobject points to the object that received the message, and msg is that BOOPSI message. The format of the message varies according to the method. The default BOOPSI message is an Msg (from &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
typedef struct {&lt;br /&gt;
    ULONG MethodID;&lt;br /&gt;
} *Msg;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
BOOPSI methods that require parameters use custom message structures. The first field of any message structure is always the method&#039;s methodID. This makes custom messages look like an Msg. The dispatcher looks at an incoming message&#039;s first field to tell what its method is. Rkmmodelclass objects respond to several rootclass methods:&lt;br /&gt;
&lt;br /&gt;
; OM_NEW&lt;br /&gt;
: This method creates a new rkmmodelclass object. It uses an opSet structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_DISPOSE&lt;br /&gt;
: This method tells an object to dispose of itself. It uses an Msg as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_SET&lt;br /&gt;
: This method tells an object to set one or more of its attribute values. It uses an opSet structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_UPDATE&lt;br /&gt;
: This method tells an object to update one or more of its attribute values. It uses an opUpdate structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_GET&lt;br /&gt;
: This method tells an object to report an attribute value. It uses an opGet structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_ADDTAIL&lt;br /&gt;
: This method tells an object to add itself to the end of an Exec list. It uses an opAddTail structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_REMOVE&lt;br /&gt;
: This method tells an object to remove itself from an Exec list. It uses an Msg as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_ADDMEMBER&lt;br /&gt;
: This method tells an object to add an object to its broadcast list. It uses an opMember structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_REMMEMBER&lt;br /&gt;
: This method tells an object to remove an object from its broadcast list. It uses an opMember structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_NOTIFY&lt;br /&gt;
: This method tells an object to broadcast an attribute change to its broadcast list. It uses an opSet structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
Of these, rkmmodelclass has to process OM_NEW, OM_SET, OM_UPDATE, and OM_GET.&lt;br /&gt;
&lt;br /&gt;
==== OM_NEW ====&lt;br /&gt;
&lt;br /&gt;
The OM_NEW method returns a pointer to a newly created BOOPSI object, or NULL if it failed to create the object. This method receives the following message structure (defined in &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct opSet {  /* The OM_NEW method uses the same structure as OM_GET */&lt;br /&gt;
    ULONG             MethodID;&lt;br /&gt;
    struct TagItem    *ops_AttrList;&lt;br /&gt;
    struct GadgetInfo *ops_GInfo;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The ops_AttrList field contains a pointer to a TagItem array of attribute/value pairs. These contain the initial values of the new object&#039;s attributes. The ops_GInfo field is always NULL for the OM_NEW method.&lt;br /&gt;
&lt;br /&gt;
Unlike other methods, when a dispatcher gets an OM_NEW message, the object pointer (recvobject from the dispatchRKMModel() prototype above) does not point to an object. It doesn&#039;t make sense for recvobject to point to an object because the idea is to create a new object, not act on an existing one.&lt;br /&gt;
&lt;br /&gt;
The pointer normally used to pass a BOOPSI object is instead used to pass the address of the object&#039;s &amp;quot;true class&amp;quot;. An object&#039;s true class is the class of which the object is an instance.&lt;br /&gt;
&lt;br /&gt;
The first thing the dispatcher does when it processes an OM_NEW message is pass the OM_NEW message on to its superclass&#039;s dispatcher. It does this using function DoSuperMethodA():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG DoSuperMethodA(Class *cl, Object *trueclass, Msg msg);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each dispatcher passes control to its superclass. Eventually the message will arrive at the rootclass dispatcher. The OM_NEW method in the rootclass dispatcher looks at the object&#039;s true class (trueclass from the prototype) to find out which class dispatcher is trying to create a new object. Note that trueclass is not necessarily the same as the current dispatcher&#039;s class (cl from the dispatchRKMModel() prototype above), although this would be the case if the object&#039;s true class is a subclass of the current dispatcher&#039;s class.&lt;br /&gt;
&lt;br /&gt;
The rootclass dispatcher uses the true class to find out how much memory to allocate for the object&#039;s instance data. Each class keeps a record of how much memory its local instance data requires. The rootclass dispatcher also looks at each class between the true class and rootclass to find out much memory the local instance data for those classes require. The rootclass dispatcher totals the amount of local instance data memory needed by the true class and each of its superclasses and allocates that much memory.&lt;br /&gt;
&lt;br /&gt;
If all goes well, the rootclass dispatcher increments a private field in the true class that keeps track of how many instances of the true class there currently are. It then returns a pointer to the newly created object and passes control back to the subclass dispatcher that called it, which is icclass in the case of rkmmodelclass. If there was a problem, the rootclass dispatcher does not increment the object count and passes back a NULL.&lt;br /&gt;
&lt;br /&gt;
When the rootclass dispatcher returns, the &#039;&#039;icclass dispatcher&#039;&#039; regains control from DoSuperMethodA(). DoSuperMethodA() will return either a pointer to the new object or else it returns NULL if there was an error. Although the rootclass dispatcher allocated all the memory the object needs, it only initialized the instance data local to rootclass. Now it&#039;s the icclass dispatcher&#039;s turn to do some work. It has to initialize the instance data that is local to icclass.&lt;br /&gt;
&lt;br /&gt;
A dispatcher finds its local instance data by using the INST_DATA() macro (defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
VOID * INST_DATA(Class *localclass, Object *object);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
INST_DATA() takes two arguments, a pointer to a class and a pointer to the object. The INST_DATA() macro returns a pointer to the instance data local to localclass. When the icclass dispatcher was called, it received three arguments, one of which was a pointer to the local class (icclass). The icclass dispatcher passes this pointer and the new object pointer it got from DoSuperMethodA() to INST_DATA() to get a pointer to the instance data local to icclass.&lt;br /&gt;
&lt;br /&gt;
After initializing its local instance data, the icclass dispatcher passes control back to the modelclass dispatcher, which in turn, initializes the instance data local to modelclass. Finally, the rkmmodelclass dispatcher regains control and now has to take care of its local instance data.&lt;br /&gt;
&lt;br /&gt;
To find its local instance data, the rkmmodelclass dispatcher needs a pointer to its Class and a pointer to the new object. The dispatcher function gets its Class pointer as its first argument (cl from the dispatchRKMModel() prototype above). It gets the new object pointer as the return value from DoSuperMethodA(). In this case, INST_DATA() returns a pointer to an RKMModData structure.&lt;br /&gt;
&lt;br /&gt;
Now the dispatcher has to initialize its local instance data. It has to scan through the tag list passed in the OM_NEW message looking for initial values for the RKMMOD_CurrVal and RKMMOD_Limit attributes. As an alternative, the dispatcher&#039;s OM_NEW method can use its OM_SET method to handle initializing these &amp;quot;settable&amp;quot; attributes.&lt;br /&gt;
&lt;br /&gt;
Finally, the dispatcher can return. When the dispatcher returns from an OM_NEW method, it returns a pointer to the new object.&lt;br /&gt;
&lt;br /&gt;
If the OM_NEW method fails, it should tell the partially initialized object it got from its superclass&#039;s dispatcher to dispose of itself (using OM_DISPOSE) and return NULL.&lt;br /&gt;
&lt;br /&gt;
==== OM_SET/OM_UPDATE ====&lt;br /&gt;
&lt;br /&gt;
For the OM_SET message, the rkmmodelclass dispatcher steps through the attribute/value pairs passed to it in the OM_SET message looking for the local attributes (see OM_NEW for the OM_SET message structure). The RKMMOD_Limit attribute is easy to process. Just find it and record the value in the local RKMModData.vallimit field.&lt;br /&gt;
&lt;br /&gt;
Because the function of the rkmmodelclass&#039;s OM_SET and OM_UPDATE methods are almost identical, the rkmmodelclass dispatcher handles them as the same case. The only difference is that, because the OM_UPDATE message comes from another BOOPSI object, the OM_UPDATE method can report on transitory state changes of an attribute. For example, when the user slides a BOOPSI prop gadget, that prop gadget sends out an interim OM_UPDATE message for every interim value of PGA_Top. When the user lets go of the prop gadget, the gadget sends out a final OM_UPDATE message. The OM_UPDATE message is almost identical to the OM_SET message:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#define OPUF_INTERIM    (1&amp;amp;lt;&amp;amp;lt;0)&lt;br /&gt;
struct opUpdate {                     /* the OM_NOTIFY method uses the same structure */&lt;br /&gt;
    ULONG             MethodID;&lt;br /&gt;
    struct TagItem    *opu_AttrList;&lt;br /&gt;
    struct GadgetInfo *opu_GInfo;&lt;br /&gt;
    ULONG             opu_Flags;      /* The extra field */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A dispatcher can tell the difference between an interim and final OM_UPDATE message because the OM_UPDATE message has an extra field on it for flags. If the low order bit (the OPUF_INTERIM bit) is set, this is an interim OM_UPDATE message. The interim flag is useful to a class that wants to ignore any transitory messages, processing only final attribute values. Because rkmmodelclass wants to process all changes to its attributes, it processes all OM_UPDATE messages.&lt;br /&gt;
&lt;br /&gt;
The RKMMOD_CurrVal attribute is a little more complicated to process. The dispatcher has to make sure the new current value is within the limits set by RKMMOD_Limit, then record that new value in the local RKMModData.currval field. Because other objects need to hear about changes to RKMMOD_CurrVal, the dispatcher has to send a &#039;&#039;notification&#039;&#039; request. It does this by sending itself an OM_NOTIFY message. The OM_NOTIFY message tells an object to notify its targets (its ICA_TARGET and the objects in its broadcast list) about an attribute change. The OM_NOTIFY method does this by sending OM_UPDATE messages to all of an object&#039;s targets.&lt;br /&gt;
&lt;br /&gt;
The rkmmodelclass dispatcher does not handle the OM_NOTIFY message itself. It inherits this method from modelclass, so the rkmmodelclass dispatcher passes OM_NOTIFY messages on to its superclass.&lt;br /&gt;
&lt;br /&gt;
To notify its targets, the rkmmodelclass dispatcher has to construct an OM_NOTIFY message. The OM_NOTIFY method uses the same message structure as OM_UPDATE. Using the stack-based version of DoSuperMethodA(), DoSuperMethod(), the dispatcher can build an OM_NOTIFY message on the stack:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 . . .&lt;br /&gt;
&lt;br /&gt;
struct TagItem tt[2];&lt;br /&gt;
struct opUpdate *msg;&lt;br /&gt;
 . . .&lt;br /&gt;
&lt;br /&gt;
tt[0].ti_Tag  = RKMMOD_CurrVal;  /* make a tag list.  */&lt;br /&gt;
tt[0].ti_Data = mmd-&amp;amp;gt;currval;&lt;br /&gt;
tt[1].ti_Tag  = TAG_END;&lt;br /&gt;
&lt;br /&gt;
DoSuperMethod(cl, o, OM_NOTIFY, tt, msg-&amp;amp;gt;opu__GInfo,&lt;br /&gt;
              ((msg-&amp;amp;gt;MethodID == OM_UPDATE) ? (msg-&amp;amp;gt;opu_Flags) : 0L));&lt;br /&gt;
 . . .&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because the OM_NOTIFY needs a tag list of attributes about which to issue updates, the dispatcher builds a tag list containing just the RKMMOD_CurrVal tag and its new value. The dispatcher doesn&#039;t use the tag list passed to it in the OM_UPDATE/OM_NOTIFY message because that list can contain many other attributes besides RKMMOD_CurrVal.&lt;br /&gt;
&lt;br /&gt;
The msg variable in the DoSuperMethod() call above is the OM_SET or OM_UPDATE message that was passed to the dispatcher. The dispatcher uses that structure to find a pointer to the GadgetInfo structure that the OM_NOTIFY message requires. The GadgetInfo structure comes from Intuition and contains information that BOOPSI gadgets need to render themselves. For the moment, don&#039;t worry about what the GadgetInfo structure actually does, just pass it on. The targets of an rkmmodel will probably need it.&lt;br /&gt;
&lt;br /&gt;
Notice that the dispatcher has to test to see if the message is an OM_SET or OM_UPDATE so it can account for the opu_Flags field at the end of the OM_UPDATE message.&lt;br /&gt;
&lt;br /&gt;
Processing the RKMMOD_Up and RKMMOD_Down attributes is similar to the RKMMOD_CurrVal attribute. When the dispatcher sees one of these, it has to increment or decrement the local RKMModData.currval, making sure RKMModData.currval is within limits. The dispatcher then sends an OM_NOTIFY message to the superclass about the change to RKMModData.currval.&lt;br /&gt;
&lt;br /&gt;
The return value from the dispatcher&#039;s OM_SET method depends on the what effect the attribute change has to the visual state of the objects in the rkmmodel&#039;s broadcast list. If an attribute change will not affect the visual state of the rkmmodel&#039;s objects, the OM_SET method returns zero. If the attribute change could trigger a change to the rkmmodel&#039;s objects, it returns something besides zero. For example, the rkmmodelclass OM_SET method returns 1L if an rkmmodel&#039;s RKMMOD_CurrVal, RKMMOD_Up, or RKMMOD_Down attribute is changed.&lt;br /&gt;
&lt;br /&gt;
At some point the rkmmodelclass dispatcher has to allow its superclasses to process these attributes it inherits. Normally a dispatcher lets the superclass process its attributes before attempting to process any local attributes. The rkmmodelclass dispatcher does this by passing on the OM_SET or OM_UPDATE message using DoSuperMethodA() (inheritance at work!). As an alternative, the dispatcher can use the &#039;&#039;amiga.lib&#039;&#039; function SetSuperAttrs(). See the &#039;&#039;amiga.lib&#039;&#039; Autodocs for more details on this function.&lt;br /&gt;
&lt;br /&gt;
==== OM_GET ====&lt;br /&gt;
&lt;br /&gt;
The rkmmodel only has one &amp;quot;gettable&amp;quot; attribute: RKMMOD_CurrVal, which makes processing it easy. The OM_GET message looks like this (defined in &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct opGet {&lt;br /&gt;
    ULONG MethodID;     /* OM_GET */&lt;br /&gt;
    ULONG opg_AttrID;   /* The attribute to retrieve */&lt;br /&gt;
    ULONG *opg_Storage; /* a place to put the attribute&#039;s value */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the rkmmodelclass dispatcher receives an OM_GET message with an opg_AttrID equal to RKMMOD_CurrVal, it copies the current value (RKMModData.currval) to the memory location opg_Storage points to and returns a value of TRUE. The TRUE indicates that there was no error. If opg_AttrID is not RKMMOD_CurrVal, the dispatcher should let its superclass handle this message.&lt;br /&gt;
&lt;br /&gt;
The rkmmodelclass dispatcher can take advantage of the fact that the only &amp;quot;gettable&amp;quot; attribute available to an rkmmodel is RKMMOD_CurrVal (the attributes defined by modelclass and icclass are not gettable-see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual for more details on which attributes are &amp;quot;settable&amp;quot;, &amp;quot;gettable&amp;quot;, etc.). If opg_AttrID is not RKMMOD_CurrVal, the rkmmodelclass dispatcher can return FALSE, indicating that the attribute was not &amp;quot;gettable&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
If the rkmmodelclass dispatcher comes across any other messages besides OM_NEW, OM_SET, OM_UPDATE, and OM_GET message, it blindly passes them on to its superclass for processing.&lt;br /&gt;
&lt;br /&gt;
==== Making the New Class ====&lt;br /&gt;
&lt;br /&gt;
The Intuition function MakeClass() creates a new BOOPSI class:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Class *MakeClass(UBYTE *newclassID, UBYTE *pubsuperclassID, Class *privsuperclass,&lt;br /&gt;
                 UWORD instancesize, ULONG flags);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the new class is going to be public, newclassID is a string naming the new class. If the new class is private, this field is NULL. The next two fields tell MakeClass() where to find the new class&#039;s superclass. If the superclass is public, pubsuperclassID points to a string naming that public superclass and the privsuperclass pointer is NULL. If the superclass is private, privsuperclass points to that superclass&#039;s Class structure and pubsuperclassID is NULL. The size of the new class&#039;s local instance data is instancesize. The last parameter, flags, is for future enhancement. For now, make this zero.&lt;br /&gt;
&lt;br /&gt;
If it is successful, MakeClass() returns a pointer to the new class, otherwise it returns NULL. When MakeClass() is successful, it also takes measures to make sure no one can &amp;quot;close&amp;quot; the new class&#039;s superclass (using FreeClass()). It does this by incrementing a private field of the superclass that keeps track of how many subclasses the superclass currently has.&lt;br /&gt;
&lt;br /&gt;
After successfully creating a class, an application has to tell the class where its dispatcher is. The Class pointer (defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;) returned by MakeClass() contains a Hook structure called cl_Dispatcher, which is used to call the dispatcher. The application has to initialize this hook:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
myclass-&amp;amp;gt;cl_Dispatcher.h_Entry = HookEntry; /* &amp;amp;lt;--- HookEntry() is defined in amiga.lib */&lt;br /&gt;
myclass-&amp;amp;gt;cl_Dispatcher.h_SubEntry = dispatchRKMModel;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The h_Entry field points to a function that copies the function arguments to where the dispatcher expects them. See the &#039;&#039;Callback Hooks&#039;&#039; section of the [[Utility_Library|Utility Library]] for more details.&lt;br /&gt;
&lt;br /&gt;
To make a class public instead of private, an application has to call AddClass() in addition to giving the class a name in MakeClass(). AddClass() takes one argument, a pointer to a valid Class structure that has been initialized as a public class by MakeClass(). To remove a public class added to the system with AddClass(), pass the public class pointer to RemoveClass(). See the Intuition Autodocs for more details on AddClass() and RemoveClass().&lt;br /&gt;
&lt;br /&gt;
==== RKMModel.c ====&lt;br /&gt;
&lt;br /&gt;
The following code, RKMModel.c, makes up an initialization function and the dispatcher function for a private class informally called rkmmodelclass.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* RKMModel.c - A simple custom modelclass subclass.&lt;br /&gt;
LC -cfist -b1 -y -v -j73 rkmmodel.c&lt;br /&gt;
quit ;*/&lt;br /&gt;
&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/intuition.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/classes.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/classusr.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/imageclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/cghooks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/icclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/tagitem.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/hooks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/intuition_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/utility_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/alib_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/alib_stdio_protos.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
extern struct Library *IntuitionBase, *UtilityBase;&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/****************  The attributes defined by this class  *****************************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
#define RKMMOD_CurrVal  (TAG_USER + 1) /* This attribute is the current value of the model.*******/&lt;br /&gt;
                                       /**********************************************************/&lt;br /&gt;
#define RKMMOD_Up       (TAG_USER + 2) /* These two are fake attributes that rkmmodelclass *******/&lt;br /&gt;
#define RKMMOD_Down     (TAG_USER + 3) /* uses as pulse values to increment/decrement the  *******/&lt;br /&gt;
                                       /* rkmmodel&#039;s RKMMOD_CurrVal attribute.             *******/&lt;br /&gt;
                                       /**********************************************************/&lt;br /&gt;
#define RKMMOD_Limit    (TAG_USER + 4) /* This attribute contains the upper bound of the   *******/&lt;br /&gt;
                                       /* rkmmodel&#039;s RKMMOD_CurrVal.  The rkmmodel has a   *******/&lt;br /&gt;
                                       /* static lower bound of zero.                      *******/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
&lt;br /&gt;
#define DEFAULTVALLIMIT 100L /* If the programmer doesn&#039;t set               */&lt;br /&gt;
                             /* RKMMOD_Limit, it defaults to this.          */&lt;br /&gt;
struct RKMModData {&lt;br /&gt;
    ULONG currval;       /* The instance data for this class.               */&lt;br /&gt;
    ULONG vallimit;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/**************************      The functions in this module     ********************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
void    geta4(void);                                                              /***************/&lt;br /&gt;
Class  *initRKMModClass(void);                                                    /***************/&lt;br /&gt;
BOOL    freeRKMModClass(Class *);                                                 /***************/&lt;br /&gt;
ULONG   dispatchRKMModel(Class *, Object *, Msg);                                 /***************/&lt;br /&gt;
void    NotifyCurrVal(Class *, Object *, struct opUpdate *, struct RKMModData *); /***************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/********************************   Initialize the class    **************************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
Class   *initRKMModClass(void)                /* Make the class and set     */&lt;br /&gt;
{                                             /* up the dispatcher&#039;s hook.  */&lt;br /&gt;
    Class *cl;&lt;br /&gt;
    extern ULONG HookEntry();  /*      &amp;amp;lt;-------   defined in amiga.lib.     */&lt;br /&gt;
&lt;br /&gt;
    if ( cl =  MakeClass( NULL,&lt;br /&gt;
                &amp;amp;quot;modelclass&amp;amp;quot;, NULL,&lt;br /&gt;
                sizeof ( struct RKMModData ),&lt;br /&gt;
                0 ))&lt;br /&gt;
    {&lt;br /&gt;
        cl-&amp;amp;gt;cl_Dispatcher.h_Entry = HookEntry;           /* initialize the  */&lt;br /&gt;
        cl-&amp;amp;gt;cl_Dispatcher.h_SubEntry = dispatchRKMModel; /* cl_Dispatcher   */&lt;br /&gt;
                                                         /* Hook.           */&lt;br /&gt;
    }&lt;br /&gt;
    return ( cl );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/*********************************      Free the class     ***************************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
BOOL freeRKMModClass( Class *cl )&lt;br /&gt;
{&lt;br /&gt;
    return (FreeClass(cl));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/********************************     The class Dispatcher     ***********************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
ULONG dispatchRKMModel(Class *cl, Object *o, Msg msg)&lt;br /&gt;
{&lt;br /&gt;
    struct RKMModData *mmd;&lt;br /&gt;
    APTR retval = NULL;  /* A generic return value used by this class&#039;s methods.  The            */&lt;br /&gt;
                         /* meaning of this field depends on the method.  For example,           */&lt;br /&gt;
                         /* OM_GET uses this a a boolean return value, while OM_NEW              */&lt;br /&gt;
                         /* uses it as a pointer to the new object.                              */&lt;br /&gt;
    geta4();    /* SAS/C and Manx function - makes sure A4 contains global data pointer.         */&lt;br /&gt;
&lt;br /&gt;
    switch (msg-&amp;amp;gt;MethodID)&lt;br /&gt;
    {&lt;br /&gt;
        case OM_NEW:     /* Pass message onto superclass first so it can set aside the memory    */&lt;br /&gt;
                         /* for the object and take care of superclass instance data.            */&lt;br /&gt;
            if (retval = (APTR)DoSuperMethodA(cl, o, msg))&lt;br /&gt;
            {            /************************************************************************/&lt;br /&gt;
                         /* For the OM_NEW method, the object pointer passed to the dispatcher   */&lt;br /&gt;
                         /* does not point to an object (how could it? The object doesn&#039;t exist  */&lt;br /&gt;
                         /* yet.)  DoSuperMethodA() returns a pointer to a newly created         */&lt;br /&gt;
                         /* object. INST_DATA() is a macro defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;      */&lt;br /&gt;
                         /* that returns a pointer to the object&#039;s instance data that is local   */&lt;br /&gt;
                         /* to this class. For example, the instance data local to this class    */&lt;br /&gt;
                         /* is the RKMModData structure defined above.                           */&lt;br /&gt;
                         /************************************************************************/&lt;br /&gt;
                mmd = INST_DATA(cl, retval);&lt;br /&gt;
                mmd-&amp;amp;gt;currval = GetTagData(RKMMOD_CurrVal, 0L, /* initialize object&#039;s attributes. */&lt;br /&gt;
                                          ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList);&lt;br /&gt;
                mmd-&amp;amp;gt;vallimit = GetTagData(RKMMOD_Limit, DEFAULTVALLIMIT,&lt;br /&gt;
                                           ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList);&lt;br /&gt;
            }&lt;br /&gt;
            break;&lt;br /&gt;
        case OM_SET:&lt;br /&gt;
        case OM_UPDATE:&lt;br /&gt;
            mmd = INST_DATA(cl, o);&lt;br /&gt;
            DoSuperMethodA(cl, o, msg);   /* Let the superclasses set their attributes first.    */&lt;br /&gt;
            {&lt;br /&gt;
                struct TagItem *tstate, *ti;    /* grab some temp variables off of the stack.    */&lt;br /&gt;
&lt;br /&gt;
                ti = ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList;&lt;br /&gt;
                tstate = ti;&lt;br /&gt;
&lt;br /&gt;
                        /* Step through all of the attribute/value pairs in the list.  Use the   */&lt;br /&gt;
                        /* utility.library tag functions to do this so they can properly process */&lt;br /&gt;
                        /* special tag IDs like TAG_SKIP, TAG_IGNORE, etc.                       */&lt;br /&gt;
&lt;br /&gt;
                while (ti = NextTagItem(&amp;amp;amp;tstate))    /* Step through all of the attribute/value  */&lt;br /&gt;
                {                 /* pairs in the list. Use the utility.library tag functions    */&lt;br /&gt;
                                  /* to do this so they can properly process special tag IDs     */&lt;br /&gt;
                                  /* like TAG_SKIP, TAG_IGNORE, etc.                             */&lt;br /&gt;
                    switch (ti-&amp;amp;gt;ti_Tag)&lt;br /&gt;
                    {&lt;br /&gt;
                        case RKMMOD_CurrVal:&lt;br /&gt;
                            if ((ti-&amp;amp;gt;ti_Data) &amp;amp;gt; mmd-&amp;amp;gt;vallimit) ti-&amp;amp;gt;ti_Data =&lt;br /&gt;
                                    mmd-&amp;amp;gt;vallimit;&lt;br /&gt;
                            mmd-&amp;amp;gt;currval = ti-&amp;amp;gt;ti_Data;&lt;br /&gt;
                            NotifyCurrVal(cl, o, msg, mmd);&lt;br /&gt;
                            retval = (APTR)1L;  /* Changing RKMMOD_CurrVal can cause a visual    */&lt;br /&gt;
                            break;              /* change to gadgets in the rkmmodel&#039;s broadcast */&lt;br /&gt;
                                                /* list.  The rkmmodel has to tell the applica-  */&lt;br /&gt;
                                                /* tion by returning a value besides zero.       */&lt;br /&gt;
                        case RKMMOD_Up:&lt;br /&gt;
                            mmd-&amp;amp;gt;currval++;&lt;br /&gt;
&lt;br /&gt;
                                 /* Make sure the current value is not greater than value limit. */&lt;br /&gt;
                            if ((mmd-&amp;amp;gt;currval) &amp;amp;gt; mmd-&amp;amp;gt;vallimit) mmd-&amp;amp;gt;currval = mmd-&amp;amp;gt;vallimit;&lt;br /&gt;
                            NotifyCurrVal(cl, o, msg, mmd);&lt;br /&gt;
                            retval = (APTR)1L;  /* Changing RKMMOD_Up can cause a visual         */&lt;br /&gt;
                            break;              /* change to gadgets in the rkmmodel&#039;s broadcast */&lt;br /&gt;
                                                /* list.  The rkmmodel has to tell the applica-  */&lt;br /&gt;
                                                /* tion by returning a value besides zero.       */&lt;br /&gt;
                        case RKMMOD_Down:&lt;br /&gt;
                            mmd-&amp;amp;gt;currval--;&lt;br /&gt;
                                    /* Make sure currval didn&#039;t go negative. */&lt;br /&gt;
                            if ((LONG)(mmd-&amp;amp;gt;currval) == -1L)&lt;br /&gt;
                                mmd-&amp;amp;gt;currval = 0L;&lt;br /&gt;
                            NotifyCurrVal(cl, o, msg, mmd);&lt;br /&gt;
                            retval = (APTR)1L;  /* Changing RKMMOD_Down can cause a visual       */&lt;br /&gt;
                            break;              /* change to gadgets in the rkmmodel&#039;s broadcast */&lt;br /&gt;
                                                /* list.  The rkmmodel has to tell the applica-  */&lt;br /&gt;
                                                /* tion by returning a value besides zero.       */&lt;br /&gt;
                        case RKMMOD_Limit:&lt;br /&gt;
                            mmd-&amp;amp;gt;vallimit = ti-&amp;amp;gt;ti_Data; /* Set the limit.  Note that this does  */&lt;br /&gt;
                            break;                       /* not do bounds checking on the        */&lt;br /&gt;
                                                         /* current RKMModData.currval value.    */&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            break;&lt;br /&gt;
        case OM_GET:                     /* The only attribute that is &amp;amp;quot;gettable&amp;amp;quot; in this        */&lt;br /&gt;
            mmd = INST_DATA(cl, o);      /* class or its superclasses is RKMMOD_CurrVal.         */&lt;br /&gt;
            if ((((struct opGet *)msg)-&amp;amp;gt;opg_AttrID) == RKMMOD_CurrVal)&lt;br /&gt;
            {&lt;br /&gt;
                *(((struct opGet *)msg)-&amp;amp;gt;opg_Storage) = mmd-&amp;amp;gt;currval;&lt;br /&gt;
                retval = (Object *)TRUE;&lt;br /&gt;
            }&lt;br /&gt;
            else retval = (APTR)DoSuperMethodA(cl, o, msg);&lt;br /&gt;
            break;&lt;br /&gt;
        default:       /* rkmmodelclass does not recognize the methodID, so let the superclass&#039;s */&lt;br /&gt;
                       /* dispatcher take a look at it.                                          */&lt;br /&gt;
            retval = (APTR)DoSuperMethodA(cl, o, msg);&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
    return((ULONG)retval);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
void NotifyCurrVal(Class *cl, Object *o, struct opUpdate *msg, struct RKMModData *mmd)&lt;br /&gt;
{&lt;br /&gt;
    struct TagItem tt[2];&lt;br /&gt;
&lt;br /&gt;
    tt[0].ti_Tag = RKMMOD_CurrVal; /* make a tag list.  */&lt;br /&gt;
    tt[0].ti_Data = mmd-&amp;amp;gt;currval;&lt;br /&gt;
    tt[1].ti_Tag = TAG_DONE;&lt;br /&gt;
                                /* If the RKMMOD_CurrVal changes, we want everyone to know about */&lt;br /&gt;
    DoSuperMethod(cl, o,        /* it. Theoretically, the class is supposed to send itself a     */&lt;br /&gt;
             OM_NOTIFY,         /* OM_NOTIFY message. Because this class lets its superclass     */&lt;br /&gt;
             tt,                /* handle the OM_NOTIFY message, it skips the middleman and      */&lt;br /&gt;
             msg-&amp;amp;gt;opu_GInfo,    /* sends the OM_NOTIFY directly to its superclass.               */&lt;br /&gt;
&lt;br /&gt;
             ((msg-&amp;amp;gt;MethodID == OM_UPDATE) ? (msg-&amp;amp;gt;opu_Flags) : 0L)); /* If this is an OM_UPDATE */&lt;br /&gt;
                                /* method, make sure the part the OM_UPDATE message adds to the  */&lt;br /&gt;
                                /* OM_SET message gets added.  That lets the dispatcher handle   */&lt;br /&gt;
}                               /* OM_UPDATE and OM_SET in the same case.                        */&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Below is a diagram showing how an application could use an rkmmodelclass object:&lt;br /&gt;
&lt;br /&gt;
Figure 12-5 Rkmmodelclass Object Diagram&lt;br /&gt;
&lt;br /&gt;
In this diagram, the application uses buttongclass BOOPSI gadgets to send the rkmmodelclass the RKMMOD_Up and RKMMOD_Down attribute pulses.&lt;br /&gt;
&lt;br /&gt;
The example takes advantage of an odd feature of buttongclass. When the user clicks on a buttongclass gadget, it sends an OM_UPDATE to its ICA_TARGET, even though no BOOPSI attribute of buttongclass has changed. It does this because it&#039;s a convenient way to report button clicks.&lt;br /&gt;
&lt;br /&gt;
Whenever a gadget sends a notification, the list of attribute/value pairs in the OM_NOTIFY message always contains the gadget&#039;s GA_ID. This is an easy way for the button to inform its target of its ID so the target knows which gadget sent the OM_UPDATE message. When a buttongclass sends a notification because of a button click, it only sends out an OM_UPDATE about its GA_ID because none of its attributes changed.&lt;br /&gt;
&lt;br /&gt;
When the user clicks one of the buttons in the rkmmodelclass diagram, the button uses an ICA_MAP to map its GA_ID to one of the &amp;quot;dummy&amp;quot; pulse attributes, RKMMOD_Up and RKMMOD_Down. When the rkmmodel receives the OM_UPDATE message about RKMMOD_Up or RKMMOD_Down, it increments or decrements its internal value.&lt;br /&gt;
&lt;br /&gt;
There is one more important thing to note about rkmmodelclass. Looking at the rkmmodelclass Object diagram above, an rkmmodel&#039;s RKMMOD_CurrVal changes because it received an OM_UPDATE message from one of its gadgets. RKMMOD_CurrVal can also change if the application explicitly set RKMMOD_CurrVal using SetAttrs() or SetGadgetAttrs().&lt;br /&gt;
&lt;br /&gt;
The primary difference between the OM_SET message that SetAttrs() sends and the OM_SET message that SetGadgetAttrs() sends is that SetAttrs() passes a NULL in opSet.ops_GInfo instead of a GadgetInfo pointer. This doesn&#039;t present a problem for the rkmmodel object, because it doesn&#039;t use the GadgetInfo structure. The problem is that when the rkmmodel notifies its targets, some of which are gadgets, they can&#039;t update their visual state because they need a GadgetInfo to render themselves. For this reason, the rkmmodelclass dispatcher returns a positive non-zero value when an attribute change occurs that could cause a change in the visual state of any objects in its broadcast list. An application that uses rkmmodelclass must test the return value when calling SetAttrs() on an rkmmodelclass object to tell if the attribute change requires a visual refresh of the gadgets (see the Intuition Autodocs for RefreshGadgets()).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;BOOPSI Dispatchers Can Execute on Intuition&#039;s Context.&#039;&#039; Notice that the gadgets in the figure above send OM_UPDATE messages to the rkmmodel when the user manipulates them. Because Intuition handles the user input that triggers the OM_UPDATE messages, Intuition itself is sending the OM_UPDATE messages. This means the rkmmodelclass dispatcher must be able to run on Intuition‚Äôs context, which puts some limitations on what the dispatcher is permitted to do: it can&#039;t use dos.library, it can&#039;t wait on application signals or message ports and it can&#039;t call any Intuition functions which might wait on Intuition.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Although rkmmodelclass serves as an example of a class, it leaves a little to be desired in a real-world implementation. To create the &amp;quot;prop-integer-up/down&amp;quot; super gadget from the diagram above, the application has to create, initialize, and link nine BOOPSI objects, which is tedious, especially if the application needs several of these super gadgets. Ideally, all these functions would be rolled into some subclass of gadgetclass. If there were such a class, an application would only have to create one instance of this subclass to get such a gadget. When the subclass received an OM_NEW message, it would take care of creating, initializing, and linking all of the BOOPSI objects that make up the whole super gadget.&lt;br /&gt;
&lt;br /&gt;
=== White Boxes: The Transparent Base Classes ===&lt;br /&gt;
&lt;br /&gt;
BOOPSI gadgets and images were designed to be backwards compatible with the old Intuition Gadgets and Images, so as part of their instance data, both types of objects have the old Intuition structures built into them. When NewObject() creates a new gadget or image object, the pointer it returns points to the object&#039;s embedded Gadget or Image corresponding structure. Because Intuition can tell the difference between BOOPSI images and gadgets and the original images and gadgets, applications can use BOOPSI images and gadgets interchangeably with the older Intuition entities.&lt;br /&gt;
&lt;br /&gt;
Although normally considered a &amp;quot;programming sin&amp;quot;, in some cases it is legal for class dispatchers to directly manipulate some internal fields of certain BOOPSI objects. For compatibility reasons, a BOOPSI image or gadget object contains an actual Image or Gadget structure. These objects are instances of the &#039;&#039;Transparent Base Classes&#039;&#039;, imageclass and gadgetclass.&lt;br /&gt;
&lt;br /&gt;
To change an attribute of a BOOPSI object, you normally invoke the set method, OM_SET. The Intuition functions SetAttrs() and SetGadgetAttrs() invoke this method. A BOOPSI class is informed of any attribute change at that time, allowing it to react to this change. The reaction can include validating the changed attribute, changing other attributes to match, or informing other objects of the change. That is the inherent advantage of using function calls to change attributes.&lt;br /&gt;
&lt;br /&gt;
When using conventional images and gadgets, you generally modify the structure&#039;s fields directly. This operation is very fast. For conventional images and gadgets, there is no class that needs to know about the changes, so there is no problem. However, this is untrue of BOOPSI images and gadgets. Although directly modifying the BOOPSI object&#039;s internal structure would provide a performance increase over using the BOOPSI OM_SET mechanism, altering a BOOPSI object&#039;s internal structure directly will not give the class the opportunity to react to any structure changes. This violates the BOOPSI concept, and therefore cannot be done in general.&lt;br /&gt;
&lt;br /&gt;
In order to provide a balance between the flexibility of function-access and the performance of direct-access, the transparent base classes imageclass and gadgetclass do not depend on being informed of changes to certain fields in the internal Image and Gadget structures. This means that it is OK for the dispatchers of direct subclasses of imageclass and gadgetclass to modify specific fields of BOOPSI images or gadgets. Applications and indirect subclass dispatchers of imageclass or gadgetclass may not modify those fields, since their parent classes may depend on hearing about changes to these fields, which the SetAttrs() call (or a similar function) provides.&lt;br /&gt;
&lt;br /&gt;
For dispatchers of direct subclasses of imageclass, the following are the only fields of the Image structure that are alterable by application programs:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| LeftEdge || Width || ImageData&lt;br /&gt;
|-&lt;br /&gt;
| TopEdge || Height || PlanePick&lt;br /&gt;
|-&lt;br /&gt;
| PlaneOnOff&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For dispatchers of direct subclasses of gadgetclass, the following are the only fields of the Gadget structure that are alterable by application programs:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| LeftEdge || Flags || GadgetText&lt;br /&gt;
|-&lt;br /&gt;
| TopEdge || GadgetType || SpecialInfo&lt;br /&gt;
|-&lt;br /&gt;
| Width || GadgetRender || Activation&lt;br /&gt;
|-&lt;br /&gt;
| Height || SelectRender&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Under no circumstances may an application or an indirect subclass modify one of these fields, even if the subclass knows the superclasses do not depend on notification for this field. This is the only way to preserve the possibility for future enhancements to that superclass. Note that these fields are not alterable while the gadget or image object is in use (for example, when it is attached to a window).&lt;br /&gt;
&lt;br /&gt;
== BOOPSI Gadgets ==&lt;br /&gt;
&lt;br /&gt;
One of the major enhancements to Intuition is the implementation of customizable BOOPSI gadgets. BOOPSI gadgets are not limited by dependencies upon Intuition Image and Gadget structures. Unlike Release 1.3 gadgets, which were handled exclusively by Intuition, BOOPSI gadgets handle their own rendering and their own user input.&lt;br /&gt;
&lt;br /&gt;
Since BOOPSI gadgets draw themselves, there is almost no restriction on what they can look like. A BOOPSI gadget can use graphics.library RastPort drawing functions to draw vector-based imagery which the gadget can scale to any dimension. Instead of just a two-state Boolean gadget, a BOOPSI gadget can have any number of states, each of which has its own imagery. If a programmer wanted to he could even make a BOOPSI gadget that uses the animation system to render itself.&lt;br /&gt;
&lt;br /&gt;
Because BOOPSI gadgets handle their own input, they see all the user&#039;s input, which the gadget is free to interpret. While the user has a BOOPSI gadget selected, the gadget can track mouse moves, process mouse and keyboard key presses, or watch the timer events.&lt;br /&gt;
&lt;br /&gt;
The power of a BOOPSI gadget is not limited to its ability to handle its own rendering and user input. BOOPSI gadgets are also BOOPSI objects so the gain all the benefits BOOPSI provides. This means all BOOPSI gadgets inherit the methods and attributes from their superclasses. BOOPSI gadgets can use BOOPSI images to take care of rendering their imagery. A BOOPSI gadget could be a &amp;quot;composite&amp;quot; gadget that is composed of several BOOPSI gadgets, images, and models.&lt;br /&gt;
&lt;br /&gt;
=== The BOOPSI Gadget Methods ===&lt;br /&gt;
&lt;br /&gt;
Intuition drives a BOOPSI gadget by sending it BOOPSI messages. Intuition uses a series of five BOOPSI methods:&lt;br /&gt;
&lt;br /&gt;
; GM_RENDER&lt;br /&gt;
: This method tells the gadget to render itself.&lt;br /&gt;
&lt;br /&gt;
; GM_HITTEST&lt;br /&gt;
: This method asks a gadget whether it has been &amp;quot;hit&amp;quot; by a mouse click.&lt;br /&gt;
&lt;br /&gt;
; GM_GOACTIVE&lt;br /&gt;
: This method asks a gadget if it wants to be the active gadget.&lt;br /&gt;
&lt;br /&gt;
; GM_HANDLEINPUT&lt;br /&gt;
: This method passes a gadget an input event.&lt;br /&gt;
&lt;br /&gt;
; GM_GOINACTIVE&lt;br /&gt;
: This method tells a gadget that it is no longer active.&lt;br /&gt;
&lt;br /&gt;
The formats of each of these BOOPSI messages differ, but they all have two things in common. Like all BOOPSI messages, each starts with their respective method ID. For each of these methods, the method ID field is followed by a pointer to a GadgetInfo structure (defined in &amp;lt;intuition/cghooks.h&amp;gt;). The GadgetInfo structure contains information about the display on which the gadget needs to render itself:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct GadgetInfo {&lt;br /&gt;
    struct Screen               *gi_Screen;&lt;br /&gt;
    struct Window               *gi_Window;     /* null for screen gadgets */&lt;br /&gt;
    struct Requester            *gi_Requester;  /* null if not GTYP_REQGADGET */&lt;br /&gt;
&lt;br /&gt;
    /* rendering information: don&#039;t use these without cloning/locking.&lt;br /&gt;
     * Official way is to call ObtainGIRPort()&lt;br /&gt;
     */&lt;br /&gt;
    struct RastPort             *gi_RastPort;&lt;br /&gt;
    struct Layer                *gi_Layer;&lt;br /&gt;
&lt;br /&gt;
    /* copy of dimensions of screen/window/g00/req(/group)&lt;br /&gt;
     * that gadget resides in.  Left/Top of this box is&lt;br /&gt;
     * offset from window mouse coordinates to gadget coordinates&lt;br /&gt;
     *  screen gadgets:                 0,0 (from screen coords)&lt;br /&gt;
     *  window gadgets (no g00):        0,0&lt;br /&gt;
     *  GTYP_GZZGADGETs (borderlayer):  0,0&lt;br /&gt;
     *  GZZ innerlayer gadget:          borderleft, bordertop&lt;br /&gt;
     *  Requester gadgets:              reqleft, reqtop&lt;br /&gt;
     */&lt;br /&gt;
    struct IBox                 gi_Domain;&lt;br /&gt;
&lt;br /&gt;
    /* these are the pens for the window or screen      */&lt;br /&gt;
    struct {&lt;br /&gt;
        UBYTE   DetailPen;&lt;br /&gt;
        UBYTE   BlockPen;&lt;br /&gt;
    }                           gi_Pens;&lt;br /&gt;
&lt;br /&gt;
    /* the Detail and Block pens in gi_DrInfo-&amp;amp;gt;dri_Pens[] are&lt;br /&gt;
     * for the screen.  Use the above for window-sensitive colors.&lt;br /&gt;
     */&lt;br /&gt;
    struct DrawInfo             *gi_DrInfo;&lt;br /&gt;
&lt;br /&gt;
    /* reserved space: this structure is extensible&lt;br /&gt;
     * anyway, but using these saves some recompilation&lt;br /&gt;
     */&lt;br /&gt;
    ULONG                       gi_Reserved[6];&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All the fields in this structure are read only.&lt;br /&gt;
&lt;br /&gt;
Although this structure contains a pointer to the gadget&#039;s RastPort structure, applications should not use it for rendering. Instead, use the intuition.library function ObtainGIRPort() to obtain a copy of the GadgetInfo&#039;s RastPort. When the gadget is finished with this RastPort, it should call ReleaseGIRPort() to relinquish the RastPort.&lt;br /&gt;
&lt;br /&gt;
==== GM_RENDER ====&lt;br /&gt;
&lt;br /&gt;
Every time Intuition feels it is necessary to redraw a BOOPSI gadget, it sends a gadget a GM_RENDER message. The GM_RENDER message (defined in &amp;lt;intuition/gadgetclass.h&amp;gt;) tells a gadget to render itself:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct gpRender&lt;br /&gt;
{&lt;br /&gt;
    ULONG             MethodID;   /* GM_RENDER */&lt;br /&gt;
    struct GadgetInfo *gpr_GInfo;&lt;br /&gt;
    struct RastPort   *gpr_RPort; /* all ready for use */&lt;br /&gt;
    LONG              gpr_Redraw; /* might be a &amp;amp;quot;highlight pass&amp;amp;quot; */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Some events that cause Intuition to send a GM_RENDER are: an application passed the gadget to OpenWindow(), the user moved or resized a gadget&#039;s window, or an application explicitly asked Intuition to refresh some gadgets.&lt;br /&gt;
&lt;br /&gt;
The GM_RENDER message contains a pointer to the gadget&#039;s RastPort so the GM_RENDER method does not have to extract it from the gpr_GInfo GadgetInfo structure using ObtainGIRPort()). The gadget renders itself according to how much imagery it needs to replace. The gpr_Redraw field contains one of three values:&lt;br /&gt;
&lt;br /&gt;
Redraw the entire gadget.&lt;br /&gt;
&lt;br /&gt;
The user has manipulated the gadget, causing a change to its imagery. Update only that part of the gadget&#039;s imagery that is effected by the user manipulating the gadget (for example, the knob and scrolling field of the prop gadget).&lt;br /&gt;
&lt;br /&gt;
If this gadget supports it, toggle to or from the highlighting imagery.&lt;br /&gt;
&lt;br /&gt;
Intuition is not the only entity that calls this method. The gadget&#039;s other methods may call this method to render the gadget when it goes through state changes. For example, as a prop gadget is following the mouse from the gadget&#039;s GM_HANDLEINPUT method, the gadget could send itself GM_RENDER messages, telling itself to update its imagery according to where the mouse has moved.&lt;br /&gt;
&lt;br /&gt;
==== GM_HITTEST ====&lt;br /&gt;
&lt;br /&gt;
When Intuition gets a left mouse button click in a window, one of the things it does is check through the window&#039;s list of gadgets to see if that click was inside the bounds of a gadget&#039;s Gadget structure (using the LeftEdge, TopEdge, Width, and Height fields). If it was (and that gadget is a BOOPSI gadget), Intuition sends that gadget a GM_HITTEST message (defined in &amp;lt;intuition/gadgetclass.h&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct gpHitTest&lt;br /&gt;
{&lt;br /&gt;
    ULONG             MethodID;     /* GM_HITTEST   */&lt;br /&gt;
    struct GadgetInfo *gpht_GInfo;&lt;br /&gt;
    struct&lt;br /&gt;
    {&lt;br /&gt;
        WORD X;                     /* Is this point inside of the gadget? */&lt;br /&gt;
        WORD Y;&lt;br /&gt;
    } gpht_Mouse;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This message contains the coordinates of the mouse click. These coordinates are relative to the upper-left of the gadget (LeftEdge, TopEdge).&lt;br /&gt;
&lt;br /&gt;
Because Intuition can only tell if the user clicked inside gadget&#039;s &amp;quot;bounding box&amp;quot;, Intuition only knows that the click was close to the gadget. Intuition uses the GM_HITTEST to ask the gadget if the click was really inside the gadget. The gadget returns GMR_GADGETHIT (defined in &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;) to tell Intuition that the user hit it, otherwise it returns zero. This method allows a gadget to be any shape or pattern, rather than just rectangular.&lt;br /&gt;
&lt;br /&gt;
==== GM_GOACTIVE/GM_HANDLEINPUT ====&lt;br /&gt;
&lt;br /&gt;
If a gadget returns GMR_GADGETHIT, Intuition will send it a GM_GOACTIVE message (defined in &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct gpInput                          /* Used by GM_GOACTIVE and GM_HANDLEINPUT */&lt;br /&gt;
{&lt;br /&gt;
    ULONG             MethodID;&lt;br /&gt;
    struct GadgetInfo *gpi_GInfo;&lt;br /&gt;
    struct InputEvent *gpi_IEvent;      /* The input event that triggered this method&lt;br /&gt;
                                         * (for GM_GOACTIVE, this can be NULL) */&lt;br /&gt;
    LONG              *gpi_Termination; /* For GADGETUP IntuiMessage.Code */&lt;br /&gt;
    struct&lt;br /&gt;
    {&lt;br /&gt;
        WORD X;                         /* Mouse position relative to upper          */&lt;br /&gt;
        WORD Y;                         /* left corner of gadget (LeftEdge, TopEdge) */&lt;br /&gt;
    } gpi_Mouse;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The GM_GOACTIVE message gives a gadget the opportunity to become the active gadget. The active gadget is the gadget that is currently receiving user input. Under normal conditions, only one gadget can be the active gadget (it is possible to have more than one active gadget using a groupgclass object (See the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual for more details).&lt;br /&gt;
&lt;br /&gt;
While a gadget is active, Intuition sends it GM_HANDLEINPUT messages. Each GM_HANDLEINPUT message corresponds to a single InputEvent structure. These InputEvents can be keyboard presses, timer events, mouse moves, or mouse button presses. The message&#039;s gpi_IEvent field points to this InputEvent structure. It&#039;s up to the GM_HANDLEINPUT method to interpret the meaning of these events and update the visual state of the gadget as the user manipulates the gadget. For example, the GM_HANDLEINPUT method of a prop gadget has to track mouse events to see where the user has moved the prop gadget&#039;s knob and update the gadget&#039;s imagery to reflect the new position of the knob.&lt;br /&gt;
&lt;br /&gt;
For the GM_GOACTIVE method, the gpi_IEvent field points to the struct InputEvent that triggered the GM_GOACTIVE message. Unlike the GM_HANDLEINPUT message, GM_GOACTIVE&#039;s gpi_IEvent can be NULL. If the GM_GOACTIVE message was triggered by a function like intuition.library&#039;s ActivateGadget() and not by a real InputEvent (like the user clicking the gadget), the gpi_IEvent field will be NULL.&lt;br /&gt;
&lt;br /&gt;
For gadgets that only want to become active as a direct result of a mouse click, this difference is important. For example, the prop gadget becomes active only when the user clicks on its knob. Because the only way the user can control the prop gadget is via the mouse, it does not make sense for anything but the mouse to activate the gadget. On the other hand, a string gadget doesn&#039;t care how it is activated because, as soon as it‚Äôs active, it gets user input from the keyboard rather than the mouse. Not all gadgets can become active. Some gadgets cannot become active because they have been temporarily disabled (their Gadget.Flags GFLG_DISABLED bit is set). Other gadgets will not become active because they don&#039;t need to process input. For example, a toggle gadget won&#039;t become active because it only needs to process one input event, the mouse click that toggles the gadget (which it gets from the GM_GOACTIVE message). If a toggle gadget gets a GM_GOACTIVE message and its gpi_IEvent field is not NULL, it will toggle its state and refuse to &amp;quot;go active&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The GM_GOACTIVE method has to take care of any visual state changes to a gadget that a GM_GOACTIVE message might trigger. For example, the toggle gadget in the previous paragraph has to take care of toggling its visual state from selected imagery to unselected imagery. If the gadget goes through a state change when it becomes the active gadget, (like when a string gadget positions its cursor) GM_GOACTIVE has to take care of this.&lt;br /&gt;
&lt;br /&gt;
The return values of both GM_GOACTIVE and GM_HANDLEINPUT tell Intuition whether or not the gadget wants to be active. A gadget&#039;s GM_GOACTIVE method returns GMR_MEACTIVE (defined in &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;) if it wants to become the active gadget. A gadget&#039;s GM_HANDLEINPUT method returns GMR_MEACTIVE if it wants to remain the active gadget. If a gadget either does not want to become or remain the active gadget, it returns one of the &amp;quot;go inactive&amp;quot; return values:&lt;br /&gt;
&lt;br /&gt;
; GMR_NOREUSE&lt;br /&gt;
: Tells Intuition to throw away the gpInput.gpi_IEvent InputEvent.&lt;br /&gt;
&lt;br /&gt;
; GMR_REUSE&lt;br /&gt;
: Tells Intuition to process the gpInput.gpi_IEvent InputEvent.&lt;br /&gt;
&lt;br /&gt;
; GMR_NEXTACTIVE&lt;br /&gt;
: Tells Intuition to throw away the gpInput.gpi_IEvent InputEvent and activate the next GFLG_TagCycle gadget.&lt;br /&gt;
&lt;br /&gt;
; GMR_PREVACTIVE&lt;br /&gt;
: Tells Intuition to throw away the gpInput.gpi_IEvent InputEvent and activate the previous GFLG_TagCycle gadget.&lt;br /&gt;
&lt;br /&gt;
GMR_NOREUSE tells Intuition that the gadget does not want to be active and to throw away the InputEvent that triggered the message. For example, an active prop gadget returns GMR_NOREUSE when the user lets go of the left mouse button (thus letting go of the prop gadget&#039;s knob).&lt;br /&gt;
&lt;br /&gt;
For the GM_HANDLEINPUT method, a gadget can also return GMR_REUSE, which tells Intuition to reuse the InputEvent. For example, if the user clicks outside the active string gadget, that string gadget returns GMR_REUSE. Intuition can now process that mouse click, which can be over another gadget. Another case where a string gadget returns GMR_REUSE is when the user pushes the right mouse button (the menu button). The string gadget becomes inactive and the menu button InputEvent gets reused. Intuition sees this event and tries to pop up the menu bar.&lt;br /&gt;
&lt;br /&gt;
For the GM_GOACTIVE method, a gadget must not return GMR_REUSE. If a gadget gets a GM_GOACTIVE message from Intuition and the message has an gpi_IEvent, the message was triggered by the user clicking on the gadget. In this case, Intuition knows that the user is trying to select the gadget. Intuition doesn&#039;t know if the gadget can be activated, but if it can be activated, the event that triggered the activation has just taken place. If the gadget cannot become active for any reason, it must not let Intuition reuse that InputEvent as the gadget has already taken care of the the event&#039;s purpose (clicking on the gadget). In essence, the user tried to activate the gadget and the gadget refused to become active.&lt;br /&gt;
&lt;br /&gt;
The other two possible return values are GMR_NEXTACTIVE and GMR_PREVACTIVE. These tell Intuition that a gadget does not want to be active and that the InputEvent should be discarded. Intuition then looks for the next (GMR_NEXTACTIVE) or previous (GMR_PREVACTIVE) gadget that has its GFLG_TABCYCLE flag set in its Gadget.Activation field (see the gadgetclass GA_TabCycle attribute in the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual).&lt;br /&gt;
&lt;br /&gt;
For both GM_GOACTIVE and GM_HANDLEINPUT, the gadget can bitwise-OR any of these &amp;quot;go inactive&amp;quot; return values with GMR_VERIFY. The GMR_VERIFY flag tells Intuition to send a GADGETUP IntuiMessage to the gadget&#039;s window. If the gadget uses GMR_VERIFY, it has to supply a value for the IntuiMessage.Code field. It does this by passing a value in the gpInput.gpi_Termination field. This field points to a long word, the lower 16-bits of which Intuition copies into the Code field. The upper 16-bits are for future enhancements, so clear these bits.&lt;br /&gt;
&lt;br /&gt;
==== GM_GOINACTIVE ====&lt;br /&gt;
&lt;br /&gt;
After an active gadget deactivates, Intuition sends it a GM_GOINACTIVE message (defined in &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct gpGoInactive&lt;br /&gt;
{&lt;br /&gt;
    ULONG             MethodID;    /* GM_GOINACTIVE */&lt;br /&gt;
    struct GadgetInfo *gpgi_GInfo;&lt;br /&gt;
&lt;br /&gt;
    /* V37 field only!  DO NOT attempt to read under V36! */&lt;br /&gt;
    ULONG             gpgi_Abort; /* gpgi_Abort=1 if gadget was aborted by Intuition   */&lt;br /&gt;
                                  /* and 0 if gadget went inactive at its own request. */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The gpgi_Abort field contains either a 0 or 1. If 0, the gadget became inactive on its own power (because the GM_GOACTIVE or GM_HANDLEINPUT method returned something besides GMR_MEACTIVE). If gpgi_Abort is 1, Intuition aborted this active gadget. Some instances where Intuition aborts a gadget include: the user clicked in another window or screen, an application removed the active gadget with RemoveGList(), and an application called ActiveWindow() on a window other than the gadget&#039;s window.&lt;br /&gt;
&lt;br /&gt;
==== The Active Gadget ====&lt;br /&gt;
&lt;br /&gt;
While a gadget is active, Intuition sends it a GM_HANDLEINPUT message for every timer pulse, mouse move, mouse click, and key press that takes place. A timer event pulse arrives about every tenth of a second. Mouse move events can arrive at a much higher rate than the timer pulses. Without even considering the keyboard, a gadget can get a lot of GM_HANDLEINPUT messages in a short amount of time. Because the active gadget has to handle a large volume of GM_HANDLEINPUT messages, the overhead of this method should be kept to a minimum.&lt;br /&gt;
&lt;br /&gt;
Because the gadget will always receive a GM_GOACTIVE message before it is active and a GM_GOINACTIVE message after it is no longer active, the gadget can use these methods to allocate, initialize, and deallocate temporary resources it needs for the GM_HANDLEINPUT method. This can significantly reduce the overhead of GM_HANDLEINPUT because it eliminates the need to allocate, initialize, and deallocate resources for every GM_HANDLEINPUT message.&lt;br /&gt;
&lt;br /&gt;
Note that the RastPort from ObtainGIRPort() is not cachable using this method. If the GM_HANDLEINPUT method needs to use a RastPort, it has to obtain and release the RastPort for every GM_HANDLEINPUT message using ObtainGIRPort() and ReleaseGIRPort().&lt;br /&gt;
&lt;br /&gt;
==== RKMButtonclass.c ====&lt;br /&gt;
&lt;br /&gt;
The following example is a sample BOOPSI gadget, RKMButClass.c. While the user has the RKMButton selected, the gadget sends an OM_UPDATE message to its ICA_TARGET for every timer event the button sees. The gadget sends notification about its RKMBUT_Pulse attribute, which is the horizontal distance in screen pixels the mouse is from the center of the button. The gadget takes care of rendering all of its imagery (as opposed to using a BOOPSI image to do it). The gadget&#039;s imagery is scalable to any dimensions and can be set (using SetGadgetAttrs()) while the gadget is in place.&lt;br /&gt;
&lt;br /&gt;
One possible use for such a gadget is as buttons for a prop gadget. If the user has the prop gadget&#039;s RKMButton selected, while the mouse is to the left of the button&#039;s center, the knob on the prop gadget moves left. While the mouse is to the right of the button&#039;s center, the knob on the prop gadget moves right. The speed at which the knob moves is proportional to the horizontal distance from the mouse to the active RKMButton.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* RKMButClass.c - Example BOOPSI gadget&lt;br /&gt;
; Execute me to compile me with Lattice 5.10b&lt;br /&gt;
LC -b1 -d0 -cfistq -v -y -j73 RKMButClass.c&lt;br /&gt;
Blink FROM LIB:c.o,RKMButClass.o TO TestBut LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/intuition.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/classes.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/classusr.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/imageclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/cghooks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/icclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/tagitem.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/hooks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/intuition_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/graphics_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/utility_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/alib_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/alib_stdio_protos.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;amp;lt;graphics/gfxmacros.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE&lt;br /&gt;
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */&lt;br /&gt;
int chkabort(void) { return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
UBYTE *vers = &amp;amp;quot;\0$VER: TestBut 37.1&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
/****************      Class specifics      ****************/&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
#define RKMBUT_Pulse   (TAG_USER + 1)&lt;br /&gt;
&lt;br /&gt;
struct ButINST&lt;br /&gt;
{&lt;br /&gt;
    LONG midX, midY; /* Coordinates of middle of gadget */&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
/* ButINST has one flag:  */&lt;br /&gt;
#define ERASE_ONLY   0x00000001 /* Tells rendering routine to */&lt;br /&gt;
                                /* only erase the gadget, not */&lt;br /&gt;
                                /* rerender a new one.  This  */&lt;br /&gt;
                                /* lets the gadget erase it-  */&lt;br /&gt;
                                /* self before it rescales.   */&lt;br /&gt;
&lt;br /&gt;
/* The functions in this module */&lt;br /&gt;
Class *initRKMButGadClass(void);&lt;br /&gt;
BOOL   freeRKMButGadClass(Class *);&lt;br /&gt;
ULONG  dispatchRKMButGad(Class *, Object *, Msg);&lt;br /&gt;
void   NotifyPulse(Class *, Object *, ULONG, LONG, struct gpInput *);&lt;br /&gt;
ULONG  RenderRKMBut(Class *, struct Gadget *, struct gpRender *);&lt;br /&gt;
void   geta4(void);&lt;br /&gt;
void   MainLoop(ULONG, ULONG);&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/* The main() function connects an RKMButClass object to a BOOPSI integer gadget, which displays */&lt;br /&gt;
/* the RKMButClass gadget&#039;s RKMBUT_Pulse value.  The code scales and move the gadget while it is */&lt;br /&gt;
/* in place.                                                                                     */&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
&lt;br /&gt;
struct TagItem pulse2int[] =&lt;br /&gt;
{&lt;br /&gt;
    {RKMBUT_Pulse, STRINGA_LongVal},&lt;br /&gt;
    {TAG_END,}&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
#define INTWIDTH  40&lt;br /&gt;
#define INTHEIGHT 20&lt;br /&gt;
&lt;br /&gt;
struct Library *IntuitionBase, *UtilityBase, *GfxBase;&lt;br /&gt;
struct Window *w;&lt;br /&gt;
Class *rkmbutcl;&lt;br /&gt;
struct Gadget *integer, *but;&lt;br /&gt;
struct IntuiMessage *msg;&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
    if (IntuitionBase = OpenLibrary(&amp;amp;quot;intuition.library&amp;amp;quot;, 37L))&lt;br /&gt;
    {&lt;br /&gt;
        if (UtilityBase = OpenLibrary(&amp;amp;quot;utility.library&amp;amp;quot;, 37L))&lt;br /&gt;
        {&lt;br /&gt;
            if (GfxBase = OpenLibrary(&amp;amp;quot;graphics.library&amp;amp;quot;, 37L))&lt;br /&gt;
            {&lt;br /&gt;
                if (w = OpenWindowTags(NULL,&lt;br /&gt;
                    WA_Flags,       WFLG_DEPTHGADGET | WFLG_DRAGBAR |&lt;br /&gt;
                                        WFLG_CLOSEGADGET | WFLG_SIZEGADGET,&lt;br /&gt;
                    WA_IDCMP,       IDCMP_CLOSEWINDOW,&lt;br /&gt;
                    WA_Width,           640,&lt;br /&gt;
                    WA_Height,          200,&lt;br /&gt;
                    TAG_END))&lt;br /&gt;
                {&lt;br /&gt;
                    WindowLimits(w, 450, 200, 640, 200);&lt;br /&gt;
&lt;br /&gt;
                    if (rkmbutcl = initRKMButGadClass())&lt;br /&gt;
                    {&lt;br /&gt;
                        if (integer = (struct Gadget *)NewObject(NULL,&lt;br /&gt;
                                       &amp;amp;quot;strgclass&amp;amp;quot;,&lt;br /&gt;
                                       GA_ID,            1L,&lt;br /&gt;
                                       GA_Top,           (w-&amp;amp;gt;BorderTop) + 5L,&lt;br /&gt;
                                       GA_Left,          (w-&amp;amp;gt;BorderLeft) + 5L,&lt;br /&gt;
                                       GA_Width,         INTWIDTH,&lt;br /&gt;
                                       GA_Height,        INTHEIGHT,&lt;br /&gt;
                                       STRINGA_LongVal,  0L,&lt;br /&gt;
                                       STRINGA_MaxChars, 5L,&lt;br /&gt;
                                       TAG_END))&lt;br /&gt;
                        {&lt;br /&gt;
                            if (but = (struct Gadget *)NewObject(rkmbutcl,&lt;br /&gt;
                                       NULL,&lt;br /&gt;
                                       GA_ID,       2L,&lt;br /&gt;
                                       GA_Top,      (w-&amp;amp;gt;BorderTop) + 5L,&lt;br /&gt;
                                       GA_Left,     integer-&amp;amp;gt;LeftEdge +&lt;br /&gt;
                                                        integer-&amp;amp;gt;Width + 5L,&lt;br /&gt;
                                       GA_Width,    40L,&lt;br /&gt;
                                       GA_Height,   INTHEIGHT,&lt;br /&gt;
                                       GA_Previous, integer,&lt;br /&gt;
                                       ICA_MAP,     pulse2int,&lt;br /&gt;
                                       ICA_TARGET,  integer,&lt;br /&gt;
                                       TAG_END))&lt;br /&gt;
                            {&lt;br /&gt;
                                AddGList(w, integer, -1, -1, NULL);&lt;br /&gt;
                                RefreshGList(integer, w, NULL, -1);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to resize gadget Height&amp;amp;quot;,&lt;br /&gt;
                                    NULL);&lt;br /&gt;
                                MainLoop(TAG_DONE, 0L);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to resize gadget Width&amp;amp;quot;,&lt;br /&gt;
                                    NULL);&lt;br /&gt;
                                MainLoop(GA_Height, 100L);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to resize gadget Y position&amp;amp;quot;,&lt;br /&gt;
                                    NULL);&lt;br /&gt;
                                MainLoop(GA_Width, 100L);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to resize gadget X position&amp;amp;quot;,&lt;br /&gt;
                                    NULL);&lt;br /&gt;
                                MainLoop(GA_Top, but-&amp;amp;gt;TopEdge + 20);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to quit&amp;amp;quot;, NULL);&lt;br /&gt;
                                MainLoop(GA_Left, but-&amp;amp;gt;LeftEdge + 20);&lt;br /&gt;
&lt;br /&gt;
                                RemoveGList(w, integer, -1);&lt;br /&gt;
                                DisposeObject(but);&lt;br /&gt;
                            }&lt;br /&gt;
                            DisposeObject(integer);&lt;br /&gt;
                        }&lt;br /&gt;
                        freeRKMButGadClass(rkmbutcl);&lt;br /&gt;
                        }&lt;br /&gt;
                    CloseWindow(w);&lt;br /&gt;
                }&lt;br /&gt;
                CloseLibrary(GfxBase);&lt;br /&gt;
                }&lt;br /&gt;
                CloseLibrary(UtilityBase);&lt;br /&gt;
        }&lt;br /&gt;
        CloseLibrary(IntuitionBase);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void MainLoop(ULONG attr, ULONG value)&lt;br /&gt;
{&lt;br /&gt;
    ULONG done = FALSE;&lt;br /&gt;
&lt;br /&gt;
    SetGadgetAttrs(but, w, NULL, attr, value, TAG_DONE);&lt;br /&gt;
&lt;br /&gt;
    while (done == FALSE)&lt;br /&gt;
    {&lt;br /&gt;
        WaitPort((struct MsgPort *)w-&amp;amp;gt;UserPort);&lt;br /&gt;
        while (msg = (struct IntuiMessage *)&lt;br /&gt;
           GetMsg((struct MsgPort *)w-&amp;amp;gt;UserPort))&lt;br /&gt;
        {&lt;br /&gt;
            if (msg-&amp;amp;gt;Class == IDCMP_CLOSEWINDOW)&lt;br /&gt;
            {&lt;br /&gt;
                done = TRUE;&lt;br /&gt;
            }&lt;br /&gt;
            ReplyMsg(msg);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
/**    Make the class and set up the dispatcher&#039;s hook    **/&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
Class   *initRKMButGadClass(void)&lt;br /&gt;
{&lt;br /&gt;
    Class *cl = NULL;&lt;br /&gt;
    extern ULONG HookEntry();     /* defined in amiga.lib */&lt;br /&gt;
&lt;br /&gt;
    if ( cl =  MakeClass( NULL,&lt;br /&gt;
                &amp;amp;quot;gadgetclass&amp;amp;quot;, NULL,&lt;br /&gt;
                sizeof ( struct ButINST ),&lt;br /&gt;
                0 ))&lt;br /&gt;
    {&lt;br /&gt;
        /* initialize the cl_Dispatcher Hook    */&lt;br /&gt;
        cl-&amp;amp;gt;cl_Dispatcher.h_Entry = HookEntry;&lt;br /&gt;
        cl-&amp;amp;gt;cl_Dispatcher.h_SubEntry = dispatchRKMButGad;&lt;br /&gt;
    }&lt;br /&gt;
    return ( cl );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
/******************     Free the class      ****************/&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
BOOL freeRKMButGadClass( Class *cl )&lt;br /&gt;
{&lt;br /&gt;
    return (FreeClass(cl));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
/**********       The RKMBut class dispatcher      *********/&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
ULONG dispatchRKMButGad(Class *cl, Object *o, Msg msg)&lt;br /&gt;
{&lt;br /&gt;
    struct ButINST *inst;&lt;br /&gt;
    ULONG retval = FALSE;&lt;br /&gt;
    Object *object;&lt;br /&gt;
&lt;br /&gt;
    /* SAS/C and Manx function to make sure register A4&lt;br /&gt;
       contains a pointer to global data */&lt;br /&gt;
    geta4();&lt;br /&gt;
&lt;br /&gt;
    switch (msg-&amp;amp;gt;MethodID)&lt;br /&gt;
    {&lt;br /&gt;
        case OM_NEW:       /* First, pass up to superclass */&lt;br /&gt;
            if (object = (Object *)DoSuperMethodA(cl, o, msg))&lt;br /&gt;
            {&lt;br /&gt;
                struct Gadget *g = (struct Gadget *)object;&lt;br /&gt;
&lt;br /&gt;
                            /* Initial local instance data */&lt;br /&gt;
                inst = INST_DATA(cl, object);&lt;br /&gt;
                inst-&amp;amp;gt;midX   = g-&amp;amp;gt;LeftEdge + ( (g-&amp;amp;gt;Width) / 2);&lt;br /&gt;
                inst-&amp;amp;gt;midY   = g-&amp;amp;gt;TopEdge + ( (g-&amp;amp;gt;Height) / 2);&lt;br /&gt;
&lt;br /&gt;
                retval = (ULONG)object;&lt;br /&gt;
            }&lt;br /&gt;
            break;&lt;br /&gt;
        case GM_HITTEST:&lt;br /&gt;
                   /* Since this is a rectangular gadget this  */&lt;br /&gt;
                   /* method always returns GMR_GADGETHIT.     */&lt;br /&gt;
            retval = GMR_GADGETHIT;&lt;br /&gt;
            break;&lt;br /&gt;
        case GM_GOACTIVE:&lt;br /&gt;
            inst = INST_DATA(cl, o);&lt;br /&gt;
&lt;br /&gt;
                    /* Only become active if the GM_GOACTIVE   */&lt;br /&gt;
                    /* was triggered by direct user input.     */&lt;br /&gt;
            if (((struct gpInput *)msg)-&amp;amp;gt;gpi_IEvent)&lt;br /&gt;
            {&lt;br /&gt;
                       /* This gadget is now active, change    */&lt;br /&gt;
                       /* visual state to selected and render. */&lt;br /&gt;
                ((struct Gadget *)o)-&amp;amp;gt;Flags |= GFLG_SELECTED;&lt;br /&gt;
                                RenderRKMBut(cl, (struct Gadget *)o, (struct gpRender *)msg);&lt;br /&gt;
                retval = GMR_MEACTIVE;&lt;br /&gt;
            }&lt;br /&gt;
            else            /* The GM_GOACTIVE was not         */&lt;br /&gt;
                            /* triggered by direct user input. */&lt;br /&gt;
                retval = GMR_NOREUSE;&lt;br /&gt;
            break;&lt;br /&gt;
        case GM_RENDER:&lt;br /&gt;
            retval = RenderRKMBut(cl, (struct Gadget *)o, (struct gpRender *)msg);&lt;br /&gt;
            break;&lt;br /&gt;
        case GM_HANDLEINPUT:   /* While it is active, this gadget sends its superclass an        */&lt;br /&gt;
                               /* OM_NOTIFY pulse for every IECLASS_TIMER event that goes by     */&lt;br /&gt;
                               /* (about one every 10th of a second).  Any object that is        */&lt;br /&gt;
                               /* connected to this gadget will get A LOT of OM_UPDATE messages. */&lt;br /&gt;
            {&lt;br /&gt;
                struct Gadget *g = (struct Gadget *)o;&lt;br /&gt;
                struct gpInput *gpi = (struct gpInput *)msg;&lt;br /&gt;
                struct InputEvent *ie = gpi-&amp;amp;gt;gpi_IEvent;&lt;br /&gt;
&lt;br /&gt;
                inst = INST_DATA(cl, o);&lt;br /&gt;
&lt;br /&gt;
                retval = GMR_MEACTIVE;&lt;br /&gt;
&lt;br /&gt;
                if (ie-&amp;amp;gt;ie_Class == IECLASS_RAWMOUSE)&lt;br /&gt;
                {&lt;br /&gt;
                    switch (ie-&amp;amp;gt;ie_Code)&lt;br /&gt;
                    {&lt;br /&gt;
                        case SELECTUP: /* The user let go of the gadget so return GMR_NOREUSE    */&lt;br /&gt;
                                       /* to deactivate and to tell Intuition not to reuse       */&lt;br /&gt;
                                       /* this Input Event as we have already processed it.      */&lt;br /&gt;
&lt;br /&gt;
                                       /*If the user let go of the gadget while the mouse was    */&lt;br /&gt;
                                       /*over it, mask GMR_VERIFY into the return value so       */&lt;br /&gt;
                                       /*Intuition will send a Release Verify (GADGETUP).        */&lt;br /&gt;
                            if ( ((gpi-&amp;amp;gt;gpi_Mouse).X &amp;amp;lt; g-&amp;amp;gt;LeftEdge) ||&lt;br /&gt;
                                 ((gpi-&amp;amp;gt;gpi_Mouse).X &amp;amp;gt; g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width) ||&lt;br /&gt;
                                 ((gpi-&amp;amp;gt;gpi_Mouse).Y &amp;amp;lt; g-&amp;amp;gt;TopEdge) ||&lt;br /&gt;
                                 ((gpi-&amp;amp;gt;gpi_Mouse).Y &amp;amp;gt; g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height) )&lt;br /&gt;
                                retval = GMR_NOREUSE | GMR_VERIFY;&lt;br /&gt;
                            else&lt;br /&gt;
                                retval = GMR_NOREUSE;&lt;br /&gt;
&lt;br /&gt;
                                           /* Since the gadget is going inactive, send a final   */&lt;br /&gt;
                                           /* notification to the ICA_TARGET.                    */&lt;br /&gt;
                            NotifyPulse(cl , o, 0L, inst-&amp;amp;gt;midX, (struct gp_Input *)msg);&lt;br /&gt;
                            break;&lt;br /&gt;
                        case MENUDOWN: /* The user hit the menu button. Go inactive and let      */&lt;br /&gt;
                                       /* Intuition reuse the menu button event so Intuition can */&lt;br /&gt;
                                       /* pop up the menu bar.                                   */&lt;br /&gt;
                            retval = GMR_REUSE;&lt;br /&gt;
&lt;br /&gt;
                                           /* Since the gadget is going inactive, send a final   */&lt;br /&gt;
                                           /* notification to the ICA_TARGET.                    */&lt;br /&gt;
                            NotifyPulse(cl , o, 0L, inst-&amp;amp;gt;midX, (struct gp_Input *)msg);&lt;br /&gt;
                            break;&lt;br /&gt;
                        default:&lt;br /&gt;
                            retval = GMR_MEACTIVE;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
                else if (ie-&amp;amp;gt;ie_Class == IECLASS_TIMER)&lt;br /&gt;
                              /* If the gadget gets a timer event, it sends an interim OM_NOTIFY */&lt;br /&gt;
                    NotifyPulse(cl, o, OPUF_INTERIM, inst-&amp;amp;gt;midX, gpi); /*     to its superclass. */&lt;br /&gt;
            }&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        case GM_GOINACTIVE:           /* Intuition said to go inactive.  Clear the GFLG_SELECTED */&lt;br /&gt;
                                      /* bit and render using unselected imagery.                */&lt;br /&gt;
            ((struct Gadget *)o)-&amp;amp;gt;Flags &amp;amp;amp;= ~GFLG_SELECTED;&lt;br /&gt;
            RenderRKMBut(cl, (struct Gadget *)o, (struct gpRender *)msg);&lt;br /&gt;
            break;&lt;br /&gt;
        case OM_SET:/* Although this class doesn&#039;t have settable attributes, this gadget class   */&lt;br /&gt;
                    /* does have scaleable imagery, so it needs to find out when its size and/or */&lt;br /&gt;
                    /* position has changed so it can erase itself, THEN scale, and rerender.    */&lt;br /&gt;
            if ( FindTagItem(GA_Width,  ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList) ||&lt;br /&gt;
                 FindTagItem(GA_Height, ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList) ||&lt;br /&gt;
                 FindTagItem(GA_Top,    ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList) ||&lt;br /&gt;
                 FindTagItem(GA_Left,   ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList) )&lt;br /&gt;
            {&lt;br /&gt;
                struct RastPort *rp;&lt;br /&gt;
                struct Gadget *g = (struct Gadget *)o;&lt;br /&gt;
&lt;br /&gt;
                WORD x,y,w,h;&lt;br /&gt;
&lt;br /&gt;
                x = g-&amp;amp;gt;LeftEdge;&lt;br /&gt;
                y = g-&amp;amp;gt;TopEdge;&lt;br /&gt;
                w = g-&amp;amp;gt;Width;&lt;br /&gt;
                h = g-&amp;amp;gt;Height;&lt;br /&gt;
&lt;br /&gt;
                inst = INST_DATA(cl, o);&lt;br /&gt;
&lt;br /&gt;
                retval = DoSuperMethodA(cl, o, msg);&lt;br /&gt;
&lt;br /&gt;
                                                          /* Get pointer to RastPort for gadget. */&lt;br /&gt;
                if (rp = ObtainGIRPort( ((struct opSet *)msg)-&amp;amp;gt;ops_GInfo) )&lt;br /&gt;
                {&lt;br /&gt;
                    UWORD *pens = ((struct opSet *)msg)-&amp;amp;gt;ops_GInfo-&amp;amp;gt;gi_DrInfo-&amp;amp;gt;dri_Pens;&lt;br /&gt;
&lt;br /&gt;
                    SetAPen(rp, pens[BACKGROUNDPEN]);&lt;br /&gt;
                    SetDrMd(rp, JAM1);                            /* Erase the old gadget.       */&lt;br /&gt;
                    RectFill(rp, x, y, x+w, y+h);&lt;br /&gt;
&lt;br /&gt;
                    inst-&amp;amp;gt;midX = g-&amp;amp;gt;LeftEdge + ( (g-&amp;amp;gt;Width) / 2); /* Recalculate where the       */&lt;br /&gt;
                    inst-&amp;amp;gt;midY = g-&amp;amp;gt;TopEdge + ( (g-&amp;amp;gt;Height) / 2); /* center of the gadget is.    */&lt;br /&gt;
&lt;br /&gt;
                                                                  /* Rerender the gadget.        */&lt;br /&gt;
                    DoMethod(o, GM_RENDER, ((struct opSet *)msg)-&amp;amp;gt;ops_GInfo, rp, GREDRAW_REDRAW);&lt;br /&gt;
                    ReleaseGIRPort(rp);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
                retval = DoSuperMethodA(cl, o, msg);&lt;br /&gt;
            break;&lt;br /&gt;
        default:          /* rkmmodelclass does not recognize the methodID, let the superclass&#039;s */&lt;br /&gt;
                          /* dispatcher take a look at it.                                       */&lt;br /&gt;
            retval = DoSuperMethodA(cl, o, msg);&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
    return(retval);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/************** Build an OM_NOTIFY message for RKMBUT_Pulse and send it to the superclass. *******/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
void NotifyPulse(Class *cl, Object *o, ULONG flags, LONG mid, struct gpInput *gpi)&lt;br /&gt;
{&lt;br /&gt;
    struct TagItem tt[3];&lt;br /&gt;
&lt;br /&gt;
    tt[0].ti_Tag = RKMBUT_Pulse;&lt;br /&gt;
    tt[0].ti_Data = mid - ((gpi-&amp;amp;gt;gpi_Mouse).X + ((struct Gadget *)o)-&amp;amp;gt;LeftEdge);&lt;br /&gt;
&lt;br /&gt;
    tt[1].ti_Tag = GA_ID;&lt;br /&gt;
    tt[1].ti_Data = ((struct Gadget *)o)-&amp;amp;gt;GadgetID;&lt;br /&gt;
&lt;br /&gt;
    tt[2].ti_Tag = TAG_DONE;&lt;br /&gt;
&lt;br /&gt;
    DoSuperMethod(cl, o, OM_NOTIFY, tt, gpi-&amp;amp;gt;gpi_GInfo, flags);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/*******************************   Erase and rerender the gadget.   ******************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
ULONG RenderRKMBut(Class *cl, struct Gadget *g, struct gpRender *msg)&lt;br /&gt;
{&lt;br /&gt;
    struct ButINST *inst = INST_DATA(cl, (Object *)g);&lt;br /&gt;
    struct RastPort *rp;&lt;br /&gt;
    ULONG retval = TRUE;&lt;br /&gt;
    UWORD *pens = msg-&amp;amp;gt;gpr_GInfo-&amp;amp;gt;gi_DrInfo-&amp;amp;gt;dri_Pens;&lt;br /&gt;
&lt;br /&gt;
    if (msg-&amp;amp;gt;MethodID == GM_RENDER)   /* If msg is truly a GM_RENDER message (not a gpInput that */&lt;br /&gt;
                                      /* looks like a gpRender), use the rastport within it...   */&lt;br /&gt;
        rp = msg-&amp;amp;gt;gpr_RPort;&lt;br /&gt;
    else                              /* ...Otherwise, get a rastport using ObtainGIRPort().     */&lt;br /&gt;
        rp = ObtainGIRPort(msg-&amp;amp;gt;gpr_GInfo);&lt;br /&gt;
&lt;br /&gt;
    if (rp)&lt;br /&gt;
    {&lt;br /&gt;
        UWORD back, shine, shadow, w, h, x, y;&lt;br /&gt;
&lt;br /&gt;
        if (g-&amp;amp;gt;Flags &amp;amp;amp; GFLG_SELECTED) /* If the gadget is selected, reverse the meanings of the  */&lt;br /&gt;
        {                             /* pens.                                                   */&lt;br /&gt;
            back   = pens[FILLPEN];&lt;br /&gt;
            shine  = pens[SHADOWPEN];&lt;br /&gt;
            shadow = pens[SHINEPEN];&lt;br /&gt;
        }&lt;br /&gt;
        else&lt;br /&gt;
        {&lt;br /&gt;
            back   = pens[BACKGROUNDPEN];&lt;br /&gt;
            shine  = pens[SHINEPEN];&lt;br /&gt;
            shadow = pens[SHADOWPEN];&lt;br /&gt;
        }&lt;br /&gt;
        SetDrMd(rp, JAM1);&lt;br /&gt;
&lt;br /&gt;
        SetAPen(rp, back);          /* Erase the old gadget.       */&lt;br /&gt;
        RectFill(rp, g-&amp;amp;gt;LeftEdge,&lt;br /&gt;
                     g-&amp;amp;gt;TopEdge,&lt;br /&gt;
                     g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width,&lt;br /&gt;
                     g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height);&lt;br /&gt;
&lt;br /&gt;
        SetAPen(rp, shadow);     /* Draw shadow edge.            */&lt;br /&gt;
        Move(rp, g-&amp;amp;gt;LeftEdge + 1, g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height);&lt;br /&gt;
        Draw(rp, g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width, g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height);&lt;br /&gt;
        Draw(rp, g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width, g-&amp;amp;gt;TopEdge + 1);&lt;br /&gt;
&lt;br /&gt;
        w = g-&amp;amp;gt;Width / 4;       /* Draw Arrows - Sorry, no frills imagery */&lt;br /&gt;
        h = g-&amp;amp;gt;Height / 2;&lt;br /&gt;
        x = g-&amp;amp;gt;LeftEdge + (w/2);&lt;br /&gt;
        y = g-&amp;amp;gt;TopEdge + (h/2);&lt;br /&gt;
&lt;br /&gt;
        Move(rp, x, inst-&amp;amp;gt;midY);&lt;br /&gt;
        Draw(rp, x + w, y);&lt;br /&gt;
        Draw(rp, x + w, y + (g-&amp;amp;gt;Height) - h);&lt;br /&gt;
        Draw(rp, x, inst-&amp;amp;gt;midY);&lt;br /&gt;
&lt;br /&gt;
        x = g-&amp;amp;gt;LeftEdge + (w/2) + g-&amp;amp;gt;Width / 2;&lt;br /&gt;
&lt;br /&gt;
        Move(rp, x + w, inst-&amp;amp;gt;midY);&lt;br /&gt;
        Draw(rp, x, y);&lt;br /&gt;
        Draw(rp, x, y  + (g-&amp;amp;gt;Height) - h);&lt;br /&gt;
        Draw(rp, x + w, inst-&amp;amp;gt;midY);&lt;br /&gt;
&lt;br /&gt;
        SetAPen(rp, shine);    /* Draw shine edge.           */&lt;br /&gt;
        Move(rp, g-&amp;amp;gt;LeftEdge, g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height - 1);&lt;br /&gt;
        Draw(rp, g-&amp;amp;gt;LeftEdge, g-&amp;amp;gt;TopEdge);&lt;br /&gt;
        Draw(rp, g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width - 1, g-&amp;amp;gt;TopEdge);&lt;br /&gt;
&lt;br /&gt;
        if (msg-&amp;amp;gt;MethodID != GM_RENDER) /* If we allocated a rastport, give it back.             */&lt;br /&gt;
            ReleaseGIRPort(rp);&lt;br /&gt;
    }&lt;br /&gt;
    else retval = FALSE;&lt;br /&gt;
    return(retval);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following are brief descriptions of functions discussed in this section. See the SDK for details on each function call.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;header&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;th align=&amp;quot;left&amp;quot;&amp;gt;&#039;&#039;&#039;Function&#039;&#039;&#039;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;th align=&amp;quot;left&amp;quot;&amp;gt;&#039;&#039;&#039;Description&#039;&#039;&#039;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;NewObjectA()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Create a new BOOPSI object (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;NewObject()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Create a new BOOPSI object (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DisposeObject()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Dispose of a BOOPSI object.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;SetAttrs()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Set one or more of a BOOPSI object&#039;s attributes (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;SetGadgetAttrs()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Set one or more of a BOOPSI object&#039;s attributes (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;GetAttr()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Obtain an attribute from a BOOPSI object.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;MakeClass()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Create a new private or public BOOPSI class.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;FreeClass()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Free a BOOPSI class created by MakeClass().&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;AddClass()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Add a public BOOPSI class to Intuition&#039;s internal list&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;of public classes.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;RemoveClass()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Remove a public BOOPSI class that was added to Intuition&#039;s&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;internal list with AddClass().&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;ObtainGIRPort()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Set up a RastPort for use by a BOOPSI gadget dispatcher.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;ReleaseGIRPort()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Free a RastPort set up by ReleaseGIRPort().&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DoMethodA()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DoMethod()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DoSuperMethodA()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object as if the object&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;was an instance of its class&#039;s superclass (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DoSuperMethod()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object as if the object&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;was an instance of its class&#039;s superclass (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;CoerceMethodA()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object as if the object&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;was an instance of the specified class (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;CoerceMethod()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object as if the object&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;was an instance of the specified class (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;SetSuperAttrs()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI OM_SET message to the BOOPSI object&#039;s superclass.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
	<entry>
		<id>https://wiki.amigaos.net/w/index.php?title=BOOPSI_-_Object_Oriented_Intuition&amp;diff=3344</id>
		<title>BOOPSI - Object Oriented Intuition</title>
		<link rel="alternate" type="text/html" href="https://wiki.amigaos.net/w/index.php?title=BOOPSI_-_Object_Oriented_Intuition&amp;diff=3344"/>
		<updated>2012-07-06T05:17:31Z</updated>

		<summary type="html">&lt;p&gt;Gianfranco Gignina: /* Function Reference */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{WIP}}&lt;br /&gt;
== BOOPSI - Object Oriented Intuition ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;BOOPSI&#039;&#039; is an acronym for &#039;&#039;Basic Object Oriented Programming System for Intuition&#039;&#039;. Using the Object Oriented Programming (OOP) model, BOOPSI represents certain Intuition entities, like Gadgets and Images, as objects.&lt;br /&gt;
&lt;br /&gt;
There are many advantages to using BOOPSI:&lt;br /&gt;
&lt;br /&gt;
* BOOPSI makes Intuition customizable and extensible. BOOPSI programmers can create new types of BOOPSI objects to suit the needs of their applications. These new types of objects are part of Intuition and can be made public so other applications can use them. Because applications can share the new types, application writers don&#039;t have to waste their time duplicating each other&#039;s efforts writing the same objects.&lt;br /&gt;
&lt;br /&gt;
* New types of BOOPSI objects can build on old types of BOOPSI objects, inheriting the old object&#039;s behavior. The result is that BOOPSI programmers don&#039;t have to waste their time building new objects from scratch, they simply add to the existing object.&lt;br /&gt;
&lt;br /&gt;
* OOP and BOOPSI apply the concept of interchangeable parts to Intuition programming. A BOOPSI programmer can combine different BOOPSI objects (like gadgets and images) to create an entire Graphical User Interface (GUI). The BOOPSI programmer doesn&#039;t have take the time to understand or implement the inner workings of these objects. The BOOPSI programmer only needs to know how to interact with BOOPSI objects and how to make them interact with each other.&lt;br /&gt;
&lt;br /&gt;
* BOOPSI objects have a consistent, command-driven interface. To the BOOPSI programmer, there is no difference between displaying a text, border, or bitmap-based BOOPSI image, even though they are rendered quite differently. Each image object accepts a single command to tell it to render itself.&lt;br /&gt;
&lt;br /&gt;
Before reading this chapter, you should already be familiar with several Amiga concepts. BOOPSI is built on top of Intuition and uses many of its structures. These include Intuition gadgets, images, and windows. BOOPSI also uses the tag concept to pass parameters. The &amp;quot;Utility Library&amp;quot; chapter of this manual discusses tags. [[Utility_Library|Utility Library]] also discusses callback Hooks, which are important to the later sections of this chapter.&lt;br /&gt;
&lt;br /&gt;
== OOP Overview ==&lt;br /&gt;
&lt;br /&gt;
Understanding BOOPSI requires an understanding of several of the concepts behind Object Oriented Programming. This section is a general overview of these concepts as they pertain to BOOPSI. Because BOOPSI is in part based on the concepts present in the OOP language &#039;&#039;Smalltalk&#039;&#039;, a reference book on &#039;&#039;Smalltalk&#039;&#039; may provide a deeper understanding of BOOPSI in general. Timothy Budd&#039;s book entitled &#039;&#039;A Little Smalltalk&#039;&#039; (Addison-Wesley Publishing ISBN 0-201-10698-1) is a good start.&lt;br /&gt;
&lt;br /&gt;
In the BOOPSI version of the Object Oriented Programming model, everything is an &#039;&#039;Object&#039;&#039;. For example, a proportional gadget named myprop is an object. Certain objects have similar characteristics and can be classified into groups called &#039;&#039;classes&#039;&#039;. As objects, Rover the dog, Bob the cat, and Sam the bird are all distinct objects but they all have something in common, they can all be classified as animals. As objects, myprop the proportional gadget, mystring the string gadget, and mybutton the button gadget all have something in common, they can all be classified as gadgets. A specific object is an &#039;&#039;instance&#039;&#039; of a particular class (&amp;quot;Rover&amp;quot; is an instance of class &amp;quot;animal&amp;quot;, &amp;quot;myslidergadget&amp;quot; is an instance of class &amp;quot;gadget&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
Notice that, although Rover, Bob, and Sam can all be classified as animals, each belongs to a subgroup of the animal class. &amp;quot;Rover&amp;quot; is an instance of class &amp;quot;dog&amp;quot;, &amp;quot;Bob&amp;quot; is an instance of class &amp;quot;cat&amp;quot;, and &amp;quot;Sam&amp;quot; is an instance of class &amp;quot;bird&amp;quot;. Because each of these animal types share common characteristics, each type makes up its own class. Because dog, cat, and bird are subclassifications of the animal class, they are known as &#039;&#039;subclasses&#039;&#039; of the animal class. Conversely, the animal class is the &#039;&#039;superclass&#039;&#039; of the dog, cat, and bird classes.&lt;br /&gt;
&lt;br /&gt;
Following the branches upward from class to superclass will bring you to a universal root category from which all objects are derived. The OOP language &#039;&#039;Smalltalk&#039;&#039; calls this class &amp;quot;Object&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Figure 12-1 Object Diagram&lt;br /&gt;
&lt;br /&gt;
Like &#039;&#039;Smalltalk&#039;&#039;, BOOPSI also has a universal root catagory, &#039;&#039;rootclass&#039;&#039;. Currently, Intuition defines three immediate subclasses of rootclass. The first, &#039;&#039;gadgetclass&#039;&#039;, is the class of BOOPSI gadgets. The second class, &#039;&#039;imageclass&#039;&#039;, makes up the class of BOOPSI images.&lt;br /&gt;
&lt;br /&gt;
Unlike gadgetclass and imageclass, the remaining subclass, &#039;&#039;icclass&#039;&#039;, does not correspond to an existing Intuition entity, it is a concept new to Intuition. Icclass, or &#039;&#039;interconnection class&#039;&#039;, allows one BOOPSI object to notify another BOOPSI object when a specific event occurs. For example, consider a BOOPSI proportional gadget and a BOOPSI image object that displays an integer value. An application can connect these two objects so that the prop gadget tells the image object the prop gadget&#039;s current value, which the image object displays. Every time the user slides the prop gadget, the prop gadget notifies the image of the change and the image updates its display to reflect the prop gadget&#039;s current integer value. Because these objects are talking to each other rather than the application, the updates happen automatically. The application doesn&#039;t have to talk to the two objects, it only has to connect them.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;sub&amp;gt;e&amp;lt;/sub&amp;gt;psf&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt;igureFig&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;2-2Simple BOOPSI Diagram&lt;br /&gt;
&lt;br /&gt;
An object&#039;s characteristics and behavior are determined by its class. Each class can define a set of &#039;&#039;attributes&#039;&#039; and a set of &#039;&#039;methods&#039;&#039; that apply to all objects of that class. An attribute is a variable characteristic of an object. For example, an attribute for the animal class could be the number of legs an animal object has. An example of a BOOPSI attribute is the X coordinate of a BOOPSI image object. The data that makes up the values of an object&#039;s attributes is collectively known as the &#039;&#039;instance data&#039;&#039; for that object.&lt;br /&gt;
&lt;br /&gt;
The behavior of an object depends upon the set of &#039;&#039;methods&#039;&#039; associated to it by its class. A method is basically a function that applies to objects of that class. An example of a BOOPSI method is the imageclass method IM_DRAW. This method tells a BOOPSI image to draw itself. All BOOPSI actions are carried out via methods.&lt;br /&gt;
&lt;br /&gt;
From the Object Diagram, two of the methods of the &amp;quot;animal&amp;quot; class could be &amp;quot;eat&amp;quot; and &amp;quot;sleep&amp;quot;. One of the methods of the &amp;quot;dog&amp;quot; class could be &amp;quot;bark&amp;quot;. Notice that instances of the &amp;quot;dog&amp;quot; class can do more than just bark, they can also eat and sleep. This is because a subclass &#039;&#039;inherits&#039;&#039; methods from its superclasses. If there were a subclass of dog called &amp;quot;attack dog&amp;quot;, all instances of that class would be able to bark, eat, and sleep, as well as &amp;quot;attack&amp;quot;. Due to inheritance, a subclass has all of the methods and all of the attributes of its superclass. For example, the IA_Height attribute is defined by imageclass. All instances of the subclasses of imageclass have their own IA_Height attribute, even though the subclasses do not explicitly define IA_Height. In turn, all instances of subclasses of the imageclass subclasses also inherit the IA_Height attribute. All classes on levels below a class will inherit its methods and attributes.&lt;br /&gt;
&lt;br /&gt;
When an application or a BOOPSI object wants another BOOPSI object to perform a method, it passes it a command in the form of a BOOPSI &#039;&#039;message&#039;&#039;. A BOOPSI message tells an object which method to perform. The message may also contain some parameters that the method requires.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;Watch Out!&#039;&#039; The term &amp;quot;message&amp;quot; used in object oriented terminology can be little confusing to the Amiga programmer because the BOOPSI message has nothing to do with an Exec message.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
BOOPSI classes can be either &#039;&#039;public&#039;&#039; or &#039;&#039;private&#039;&#039;. Public classes have ASCII names associated with them and are accessible to all applications. Private classes have no ASCII name and normally can only be accessed by the application that created the private class.&lt;br /&gt;
&lt;br /&gt;
=== Using BOOPSI ===&lt;br /&gt;
&lt;br /&gt;
There are several levels on which a programmer can use BOOPSI. The most elementary level is to use Intuition functions to create and manipulate BOOPSI objects that are instances of existing, public classes.&lt;br /&gt;
&lt;br /&gt;
At present there is a hierarchy of 14 public classes built into Intuition:&lt;br /&gt;
&lt;br /&gt;
[[File:LibFig12-3.png]]&lt;br /&gt;
&lt;br /&gt;
Figure 12-3: Class Diagram&lt;br /&gt;
&lt;br /&gt;
==== BOOPSI and Tags ====&lt;br /&gt;
&lt;br /&gt;
BOOPSI uses tag lists to pass and manipulate its attributes. To BOOPSI, each TagItem (defined in &amp;amp;lt;utility/tagitem.h&amp;amp;gt;) in a tag list is an attribute/value pair. The TagItem.ti_Tag field contains an ID for the attribute and the ti_Data field holds the attribute&#039;s value.&lt;br /&gt;
&lt;br /&gt;
For example, the string gadget class defines an attribute called STRINGA_LongVal, which is the current integer value of the gadget. Certain gadgetclass objects have an attribute called GA_Image. Its value is not an integer, it is a pointer to an image.&lt;br /&gt;
&lt;br /&gt;
Note that these tag lists can also contain utility.library Global System control tags (like TAG_SKIP and TAG_DONE), which BOOPSI uses in processing its tag lists. Any application that ends up processing these lists should do so using the tag manipulation functions from utility.library. For more information on tags and utility.library, see [[Utility_Library|Utility Library]].&lt;br /&gt;
&lt;br /&gt;
==== Creating an Object ====&lt;br /&gt;
&lt;br /&gt;
The Intuition function NewObjectA() creates a BOOPSI object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mynewobject = APTR NewObjectA(Class *privclass, UBYTE *pubclass,&lt;br /&gt;
                              struct TagItem *myattrs);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The pointer that NewObjectA() returns is a pointer to a BOOPSI object. In general, BOOPSI objects are &amp;quot;black boxes&amp;quot;. This means the inner workings of BOOPSI objects are not visible to the application programmer, so the programmer does not know what goes on inside it. This really means the inner workings of these objects are none of your business. Unless otherwise documented, only use an object pointer as a handle to the object.&lt;br /&gt;
&lt;br /&gt;
To create an object, NewObjectA() needs to know what class the new object is an instance of. To create a public class object, pass a NULL pointer in privclass and an ASCII string in pubclass naming the object&#039;s public class. The privclass pointer is used to create a private class object, which is covered in the &amp;quot;Creating a BOOPSI Class&amp;quot; section later in this chapter.&lt;br /&gt;
&lt;br /&gt;
The myattrs tag list is a list of tag/value pairs, each of which contains an initial value for some object attribute. Most objects have a set of attributes associated with them, so each attribute has a tag name. For BOOPSI gadgets and images, the attributes include some of the values from the old Gadget and Image structures (position, size, etc.).&lt;br /&gt;
&lt;br /&gt;
Most applications use the stack-based version of NewObjectA(), NewObject(), to create objects. This allows an application to build the tag list of object attributes on the stack rather than having to allocate and initialize a tag list. A code sample from a program that creates a BOOPSI string gadget might look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mystringgadget = (struct Gadget *)NewObject(NULL, &amp;amp;quot;strgclass&amp;amp;quot;,&lt;br /&gt;
                                            GA_ID,           1L,&lt;br /&gt;
                                            GA_Left,         0L,&lt;br /&gt;
                                            GA_Top,          0L,&lt;br /&gt;
                                            STRINGA_LongVal, 100L,&lt;br /&gt;
                                            TAG_END);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If NewObject() is successful, it returns a pointer to a new BOOPSI gadget object. Otherwise, it returns NULL. The class &amp;quot;strgclass&amp;quot; is one of the public classes built into Intuition. It is a class of string gadgets.&lt;br /&gt;
&lt;br /&gt;
If you look at the diagram of the public classes built into Intuition, you&#039;ll see that strgclass is a subclass of gadgetclass. In the example above, the attribute tag IDs that start with &amp;quot;GA_&amp;quot; are defined by gadgetclass and not by strgclass. This is because strgclass inherits these attributes from its superclass, gadgetclass. The other attribute, STRINGA_LongVal, is defined by strgclass. It does two things. First, it tells the object that it is a special type of string gadget which only handles an integer value rather than a generic ASCII string. Second, it passes the object its initial integer value.&lt;br /&gt;
&lt;br /&gt;
==== Disposing of an Object ====&lt;br /&gt;
&lt;br /&gt;
When an application is done with an object it has to dispose of the object. To dispose of an object, use the Intuition function DisposeObject():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
VOID DisposeObject(APTR boopsiobject);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where boopsiobject is a pointer to the BOOPSI object to be disposed. Note that some classes allow applications to connect child objects to a parent object so that when the application deletes the parent object, it automatically disposes of all of its children. Be careful not to dispose of an object that has already been disposed.&lt;br /&gt;
&lt;br /&gt;
==== Setting an Existing Object&#039;s Attributes ====&lt;br /&gt;
&lt;br /&gt;
An object&#039;s attributes are not necessarily static. An application can ask an object to set certain object attributes using the SetAttrs() function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG SetAttrs(APTR myobject, Tag1, Value1, ...);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because BOOPSI gadgets require some extra information about their display, they use a special version of this function, SetGadgetAttrs():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG SetGadgetAttrs(struct Gadget *myobject, struct Window *w,&lt;br /&gt;
                     struct Requester *r, Tag1, Value1, ...);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here myobject is a pointer to the BOOPSI object, w points to the gadget&#039;s window, r points to the gadget&#039;s requester, and the tag/value pairs are the attributes and their new values. The return value of SetAttrs() and SetGadgetAttrs() is class specific. In general, if the attribute change causes a visual change to some object, the SetAttrs()/SetGadgetAttrs() function should return a non-zero value, otherwise, these functions should return zero (see the &#039;&#039;BOOPSI Class Reference&#039;&#039; for information on the return values for specific classes). The following is an example of how to set the current integer value and gadget ID of the gadget created in the NewObject() call above:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SetGadgetAttrs(mystringgadget, mywindow, NULL, STRINGA_LongVal,   75L,&lt;br /&gt;
                                               GA_ID,             2L,&lt;br /&gt;
                                               TAG_END));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This changes two of mystringgadget&#039;s attributes. It changes the gadget&#039;s current integer value to 75 and it changes the gadget&#039;s ID number to 2.&lt;br /&gt;
&lt;br /&gt;
Note that it is not OK to call SetGadgetAttrs() on a BOOPSI object that isn&#039;t a gadget, nor is it OK to call SetAttrs() on a BOOPSI gadget.&lt;br /&gt;
&lt;br /&gt;
Not all object attributes can be set with SetGadgetAttrs()/SetAttrs(). Some classes are set up so that applications cannot change certain attributes. For example, the imagery for the knob of a proportional gadget cannot be altered after the object has been created. Whether or not a specific attribute is &amp;quot;settable&amp;quot; is class dependent. For more information about the attributes of specific classes, see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual.&lt;br /&gt;
&lt;br /&gt;
==== Getting an Object&#039;s Attributes ====&lt;br /&gt;
&lt;br /&gt;
The Intuition function GetAttr() asks an object what the value of a specific attribute is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG GetAttr(ULONG attrID, APTR myobject, ULONG *mydata);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where attrID is the attribute&#039;s ID number, myobject is the object to get the attribute from, and mydata points to a data area that will hold the attribute value. This function returns a 0 if the object doesn&#039;t recognize the attribute, otherwise it returns some non-zero value, the meaning of which depends on the class. In most cases, GetAttr() returns a 1 when it is successful.&lt;br /&gt;
&lt;br /&gt;
Not all object attributes are obtainable using the GetAttr() function. Some classes are set up so that applications cannot query the state of certain attributes. For example, using the GA_Image attribute, an application can give a BOOPSI prop gadget (propgclass) an Image structure which the gadget uses as the imagery for its knob. This attribute is not &amp;quot;gettable&amp;quot; as there is no need for an application to have to ask the gadget for the structure that the application passed it in the first place. Whether or not a specific attribute is &amp;quot;gettable&amp;quot; is class dependent. For more information about the attributes of specific classes, see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual.&lt;br /&gt;
&lt;br /&gt;
==== What About the BOOPSI Messages and Methods? ====&lt;br /&gt;
&lt;br /&gt;
According to the &amp;quot;OOP Overview&amp;quot; section, for an object to perform a method, something has to pass it a BOOPSI message. The previous section discussed using Intuition functions to ask an object to do things like set and get attributes. The functions in the previous section seem to completely ignore all that material about methods and messages. What happened to the methods and messages?&lt;br /&gt;
&lt;br /&gt;
Nothing; these functions don&#039;t ignore the OOP constructs, they just shield the programmer from them. Each of these functions corresponds to a BOOPSI method:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| NewObject() || OM_NEW&lt;br /&gt;
|-&lt;br /&gt;
| DisposeObject() || OM_DISPOSE&lt;br /&gt;
|-&lt;br /&gt;
| SetAttrs()/SetGadgetAttrs() || OM_SET&lt;br /&gt;
|-&lt;br /&gt;
| GetAttr() || OM_GET&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These methods are defined on the rootclass level, so all BOOPSI classes inherit them. The Intuition functions that correspond to these methods take care of constructing and sending a BOOPSI message with the appropriate method ID and parameters.&lt;br /&gt;
&lt;br /&gt;
=== The Public Classes ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Intuition contains 14 public classes, all of which are descendants of the rootclass. There are three primary classes that descend directly from rootclass: &#039;&#039;imageclass&#039;&#039;, &#039;&#039;gadgetclass&#039;&#039;, and &#039;&#039;icclass&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== The Imageclass Subclasses ====&lt;br /&gt;
&lt;br /&gt;
Normally, an application does not create an imageclass object. Instead, it will use a subclass of imageclass. Currently, there are four subclasses: &#039;&#039;frameiclass&#039;&#039;, &#039;&#039;sysiclass&#039;&#039;, &#039;&#039;fillrectclass&#039;&#039;, and &#039;&#039;itexticlass&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
; frameiclass&lt;br /&gt;
: An embossed or recessed rectangular frame image, that renders itself using the proper DrawInfo pens. This class is intelligent enough to bound or center its contents.&lt;br /&gt;
&lt;br /&gt;
; sysiclass&lt;br /&gt;
: The class of system images. The class includes the images for the system and GadTools gadgets.&lt;br /&gt;
&lt;br /&gt;
; fillrectclass&lt;br /&gt;
: A class of rectangle images that have frame and patternfill support.&lt;br /&gt;
&lt;br /&gt;
; itextclass&lt;br /&gt;
: A specialized image class used for rendering text.&lt;br /&gt;
&lt;br /&gt;
For more information on these classes see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual. It describes all of the existing public classes, their methods, and their attributes.&lt;br /&gt;
&lt;br /&gt;
==== The Gadgetclass Subclasses ====&lt;br /&gt;
&lt;br /&gt;
Like imageclass, applications do not normally create objects of gadgetclass, but instead create objects of its subclasses. Currently, gadgetclass has four subclasses:&lt;br /&gt;
&lt;br /&gt;
; propgclass&lt;br /&gt;
: An easy to implement, horizontal or vertical proportional gadget.&lt;br /&gt;
&lt;br /&gt;
; strgclass&lt;br /&gt;
: A string gadget.&lt;br /&gt;
&lt;br /&gt;
; groupclass&lt;br /&gt;
: A special gadget class that creates one composite gadget out of several others.&lt;br /&gt;
&lt;br /&gt;
; buttongclass&lt;br /&gt;
: A button gadget that keeps sending button presses while the user holds it down.&lt;br /&gt;
&lt;br /&gt;
buttongclass has a subclass of its own:&lt;br /&gt;
&lt;br /&gt;
; frbuttonclass&lt;br /&gt;
: A buttongclass gadget that outlines its imagery with a frame.&lt;br /&gt;
&lt;br /&gt;
For specific information on these classes, see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual.&lt;br /&gt;
&lt;br /&gt;
=== Making Gadget Objects Talk to Each Other ===&lt;br /&gt;
&lt;br /&gt;
One use for a proportional gadget is to let the user change some integer value, like the red, green, and blue components of a color. This type of prop gadget is commonly accompanied by an integer string gadget, enabling the user to adjust one integer value by either typing the value into the string gadget or by scrolling the prop gadget. Because these two gadgets reflect the value of the same integer, when the user adjusts the state of one of the gadgets (and thus changing the integer value), the other gadget should automatically update to reflect the new integer value.&lt;br /&gt;
&lt;br /&gt;
When the user manipulates a conventional gadget, the gadget sends messages to an IDCMP port to indicate the state change (for information on IDCMP, see [[Intuition_Input_and_Output_Methods|Intuition Input and Output Methods]]). To connect the string and prop gadgets from the previous paragraph, an application would have to listen for the IDCMP messages from two different gadgets, interpret the IDCMP message&#039;s meaning, and manually update the gadgets accordingly. Essentially, the application is responsible for &amp;quot;gluing&amp;quot; the gadgets together. This unnecessarily complicates an application, especially when that application already has to listen for and interpret many other events.&lt;br /&gt;
&lt;br /&gt;
BOOPSI gadgets simplify this. By setting the appropriate attributes, an application can ask a BOOPSI gadget to tell some other object when its state changes. One of the attributes defined by gadgetclass is ICA_TARGET (defined in &amp;amp;lt;intuition/icclass.h&amp;amp;gt;). The ICA_TARGET attribute points to another BOOPSI object. When certain attributes in a BOOPSI gadget change (like the integer value of a prop gadget), that gadget looks to see if it has an ICA_TARGET. If it does, it sends the target a message telling it to perform an OM_UPDATE method.&lt;br /&gt;
&lt;br /&gt;
The OM_UPDATE method is defined by rootclass. This is basically a special type of OM_SET method that is used specifically to tell a BOOPSI object that another BOOPSI object&#039;s state changed. Only BOOPSI objects send OM_UPDATE messages. Note that standard classes of BOOPSI gadgets only send out OM_UPDATE messages as a result of the user changing the state of the gadget (scrolling the prop gadget, typing a new number into an integer gadget, etc.). These gadgets do not send out OM_UPDATE messages when they receive OM_SET or OM_UPDATE messages.&lt;br /&gt;
&lt;br /&gt;
A BOOPSI propgclass object has only one attribute that triggers it to send an OM_UPDATE request: PGA_Top. This attribute contains the integer value of the prop gadget. Every time the user moves a prop gadget, the PGA_Top attribute changes. If the prop gadget has an ICA_TARGET, the prop gadget will tell the target object that the PGA_Top value has changed.&lt;br /&gt;
&lt;br /&gt;
A BOOPSI integer string gadget (a strgclass object) also has only one attribute that triggers it to send an OM_UPDATE request: STRINGA_LongVal. This value contains the integer value of the integer string gadget. Like the prop gadget, if the integer string gadget has an ICA_TARGET, when the user changes the gadget&#039;s integer value (STRINGA_LongVal), the string gadget will tell the target object that the STRINGA_LongVal value has changed.&lt;br /&gt;
&lt;br /&gt;
When a BOOPSI gadget sends an OM_UPDATE message, it passes the ID of the attribute that changed plus that attribute&#039;s new value. For example, if the user typed a 25 into a BOOPSI integer string gadget, that gadget would send an OM_UPDATE message to its ICA_TARGET saying in essence, &amp;quot;Hey, STRINGA_LongVal is 25&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
If this string gadget&#039;s ICA_TARGET is a propgclass object, the propgclass object will become confused because it has no idea what a STRINGA_LongVal attribute is. The string gadget needs to &#039;&#039;map&#039;&#039; its STRINGA_LongVal ID to the PGA_Top ID. This is what the ICA_MAP attribute is for.&lt;br /&gt;
&lt;br /&gt;
The ICA_MAP attribute is defined by gadgetclass (it is also defined for icclass-more on that later). It accepts a tag list of attribute mappings. When a gadget sends out an OM_UPDATE message, it uses this map to translate a specific attribute ID to another attribute ID, without changing the value of the attribute. Each TagItem in the ICA_MAP makes up a single attribute mapping. The TagItem.ti_Tag of the mapping is the ID of an attribute to translate. The gadget translates that attribute ID to the attribute ID in TagItem.ti_Data. For example, an ICA_MAP that maps a string gadget&#039;s STRINGA_LongVal attribute to a prop gadget&#039;s PGA_Top attribute looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct TagItem slidertostring[] = {&lt;br /&gt;
    {PGA_Top, STRINGA_LongVal},&lt;br /&gt;
    {TAG_END, }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that it is OK to have an ICA_TARGET without having an ICA_MAP. In cases where a gadget and its ICA_TARGET have a set of attributes in common, it would be unnecessary to use an ICA_MAP to match a gadget&#039;s attributes, as they already match.&lt;br /&gt;
&lt;br /&gt;
The following example, Talk2boopsi.c, creates a prop gadget and an integer string gadget which update each other without the example program having to process any messages from them.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* Talk2boopsi.c - Execute me to compile me with SAS/C 5.10b&lt;br /&gt;
LC -b1 -cfistq -v -y -j73 Talk2boopsi.c&lt;br /&gt;
Blink FROM LIB:c.o,Talk2boopsi.o TO Talk2boopsi LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit ;*/&lt;br /&gt;
/* This example creates a BOOPSI prop gadget and integer string gadget, connecting them so they */&lt;br /&gt;
/* update each other when the user changes their value.  The example program only initializes   */&lt;br /&gt;
/* the gadgets and puts them on the window; it doesn&#039;t have to interact with them to make them  */&lt;br /&gt;
/* talk to each other.                                                                          */&lt;br /&gt;
&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/tagitem.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/intuition.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt; /* contains IDs for gadget attributes */&lt;br /&gt;
#include &amp;amp;lt;intuition/icclass.h&amp;amp;gt;     /* contains ICA_MAP, ICA_TARGET       */&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/intuition_protos.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE                     /* Disable SAS/C CTRL/C handling    */&lt;br /&gt;
int CXBRK(void) { return(0); }&lt;br /&gt;
int chkabort(void) { return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
UBYTE *vers = &amp;amp;quot;\0$VER: Talk2boopsi 37.1&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
struct Library *IntuitionBase;&lt;br /&gt;
struct Window *w;&lt;br /&gt;
struct IntuiMessage *msg;&lt;br /&gt;
struct Gadget *prop, *integer;&lt;br /&gt;
&lt;br /&gt;
/* The attribute mapping lists */&lt;br /&gt;
struct TagItem prop2intmap[] =    /* This tells the prop gadget to */&lt;br /&gt;
{                                 /* map its PGA_Top attribute to  */&lt;br /&gt;
    {PGA_Top,   STRINGA_LongVal}, /* STRINGA_LongVal when it       */&lt;br /&gt;
    {TAG_END,}                    /* issues an update about the    */&lt;br /&gt;
};                                /* change to its PGA_Top value.  */&lt;br /&gt;
&lt;br /&gt;
struct TagItem int2propmap[] =    /* This tells the string gadget */&lt;br /&gt;
{                                 /* to map its STRINGA_LongVal   */&lt;br /&gt;
    {STRINGA_LongVal,   PGA_Top}, /* attribute to PGA_Top when it */&lt;br /&gt;
    {TAG_END,}                    /* issues an update.            */&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
#define PROPGADGET_ID       1L&lt;br /&gt;
#define INTGADGET_ID        2L&lt;br /&gt;
#define PROPGADGETWIDTH     10L&lt;br /&gt;
#define PROPGADGETHEIGHT    80L&lt;br /&gt;
#define INTGADGETHEIGHT     18L&lt;br /&gt;
#define VISIBLE             10L&lt;br /&gt;
#define TOTAL               100L&lt;br /&gt;
#define INITIALVAL          25L&lt;br /&gt;
#define MINWINDOWWIDTH      80&lt;br /&gt;
#define MINWINDOWHEIGHT     (PROPGADGETHEIGHT + 70)&lt;br /&gt;
#define MAXCHARS            3L&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
    BOOL done = FALSE;&lt;br /&gt;
&lt;br /&gt;
    if (IntuitionBase = OpenLibrary(&amp;amp;quot;intuition.library&amp;amp;quot;, 37L))&lt;br /&gt;
    {                                /* Open the window--notice that the window&#039;s IDCMP port     */&lt;br /&gt;
                                     /* does not listen for GADGETUP messages.                   */&lt;br /&gt;
        if (w = OpenWindowTags(NULL,&lt;br /&gt;
                 WA_Flags,       WFLG_DEPTHGADGET | WFLG_DRAGBAR |&lt;br /&gt;
                                     WFLG_CLOSEGADGET | WFLG_SIZEGADGET,&lt;br /&gt;
                 WA_IDCMP,       IDCMP_CLOSEWINDOW,&lt;br /&gt;
                 WA_MinWidth,    MINWINDOWWIDTH,&lt;br /&gt;
                 WA_MinHeight,   MINWINDOWHEIGHT,&lt;br /&gt;
                 TAG_END))&lt;br /&gt;
        {                                 /* Create a new propgclass object */&lt;br /&gt;
            if (prop = (struct Gadget *)NewObject(NULL, &amp;amp;quot;propgclass&amp;amp;quot;,&lt;br /&gt;
                GA_ID,     PROPGADGET_ID,        /* These are defined by gadgetclass and         */&lt;br /&gt;
                GA_Top,    (w-&amp;amp;gt;BorderTop) + 5L,  /* correspond to similarly named fields in      */&lt;br /&gt;
                GA_Left,   (w-&amp;amp;gt;BorderLeft) + 5L, /* the Gadget structure.                        */&lt;br /&gt;
                GA_Width,  PROPGADGETWIDTH,&lt;br /&gt;
                GA_Height, PROPGADGETHEIGHT,&lt;br /&gt;
&lt;br /&gt;
                ICA_MAP,      prop2intmap, /* The prop gadget&#039;s attribute map */&lt;br /&gt;
&lt;br /&gt;
             /* The rest of this gadget&#039;s attributes are defined by propgclass. */&lt;br /&gt;
                PGA_Total,     TOTAL,          /* This is the integer range of the prop gadget.  */&lt;br /&gt;
                PGA_Top,       INITIALVAL,     /* The initial integer value of the prop gadget.  */&lt;br /&gt;
&lt;br /&gt;
                PGA_Visible,   VISIBLE, /* This determines how much of the prop gadget area is   */&lt;br /&gt;
                                        /* covered by the prop gadget&#039;s knob, or how much of     */&lt;br /&gt;
                                        /* the gadget&#039;s TOTAL range is taken up by the prop      */&lt;br /&gt;
                                        /* gadget&#039;s knob.                                        */&lt;br /&gt;
&lt;br /&gt;
                PGA_NewLook,   TRUE,    /* Use new-look prop gadget imagery */&lt;br /&gt;
                TAG_END))&lt;br /&gt;
            {                           /* create the integer string gadget.                     */&lt;br /&gt;
                if (integer = (struct Gadget *)NewObject(NULL, &amp;amp;quot;strgclass&amp;amp;quot;,&lt;br /&gt;
                    GA_ID,      INTGADGET_ID,         /* Parameters for the Gadget structure     */&lt;br /&gt;
                    GA_Top,     (w-&amp;amp;gt;BorderTop) + 5L,&lt;br /&gt;
                    GA_Left,    (w-&amp;amp;gt;BorderLeft) + PROPGADGETWIDTH + 10L,&lt;br /&gt;
                    GA_Width,   MINWINDOWWIDTH -&lt;br /&gt;
                                  (w-&amp;amp;gt;BorderLeft + w-&amp;amp;gt;BorderRight +&lt;br /&gt;
                                  PROPGADGETWIDTH + 15L),&lt;br /&gt;
                    GA_Height,  INTGADGETHEIGHT,&lt;br /&gt;
&lt;br /&gt;
                    ICA_MAP,    int2propmap,           /* The attribute map */&lt;br /&gt;
                    ICA_TARGET, prop,                  /* plus the target.  */&lt;br /&gt;
&lt;br /&gt;
                            /* Th GA_Previous attribute is defined by gadgetclass and is used to */&lt;br /&gt;
                            /* wedge a new gadget into a list of gadget&#039;s linked by their        */&lt;br /&gt;
                            /* Gadget.NextGadget field.  When NewObject() creates this gadget,   */&lt;br /&gt;
                            /* it inserts the new gadget into this list behind the GA_Previous   */&lt;br /&gt;
                            /* gadget. This attribute is a pointer to the previous gadget        */&lt;br /&gt;
                            /* (struct Gadget *).  This attribute cannot be used to link new     */&lt;br /&gt;
                            /* gadgetsinto the gadget list of an open window or requester,       */&lt;br /&gt;
                    GA_Previous, prop,  /* use AddGList() instead.                               */&lt;br /&gt;
&lt;br /&gt;
                    STRINGA_LongVal,  INITIALVAL, /* These attributes are defined by strgclass.  */&lt;br /&gt;
                    STRINGA_MaxChars, MAXCHARS,   /* The first contains the value of the         */&lt;br /&gt;
                    TAG_END))                     /* integer string gadget. The second is the    */&lt;br /&gt;
                                                  /* maximum number of characters the user is    */&lt;br /&gt;
                                                  /* allowed to type into the gadget.            */&lt;br /&gt;
                 {&lt;br /&gt;
                    SetGadgetAttrs(prop, w, NULL, /* Because the integer string gadget did not   */&lt;br /&gt;
                        ICA_TARGET, integer,      /* exist when this example created the prop    */&lt;br /&gt;
                        TAG_END);                 /* gadget, it had to wait to set the           */&lt;br /&gt;
                                                  /* ICA_Target of the prop gadget.              */&lt;br /&gt;
&lt;br /&gt;
                    AddGList(w, prop, -1, -1, NULL);  /* Add the gadgets to the                  */&lt;br /&gt;
                    RefreshGList(prop, w, NULL, -1);  /* window and display them.                */&lt;br /&gt;
&lt;br /&gt;
                    while (done == FALSE)     /* Wait for the user to click */&lt;br /&gt;
                    {                         /* the window close gadget.   */&lt;br /&gt;
                        WaitPort((struct MsgPort *)w-&amp;amp;gt;UserPort);&lt;br /&gt;
                        while (msg = (struct IntuiMessage *)&lt;br /&gt;
                           GetMsg((struct MsgPort *)w-&amp;amp;gt;UserPort))&lt;br /&gt;
                        {&lt;br /&gt;
                            if (msg-&amp;amp;gt;Class == IDCMP_CLOSEWINDOW)&lt;br /&gt;
                                done = TRUE;&lt;br /&gt;
                            ReplyMsg(msg);&lt;br /&gt;
                        }&lt;br /&gt;
                    }&lt;br /&gt;
                    RemoveGList(w, prop, -1);&lt;br /&gt;
                    DisposeObject(integer);&lt;br /&gt;
                 }&lt;br /&gt;
                DisposeObject(prop);&lt;br /&gt;
            }&lt;br /&gt;
            CloseWindow(w);&lt;br /&gt;
        }&lt;br /&gt;
        CloseLibrary(IntuitionBase);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Making Gadgets Talk to an Application ===&lt;br /&gt;
&lt;br /&gt;
There are two questions that the example above brings to mind. The first is, &amp;quot;What happens if the user types a value into the string gadget that is beyond the bounds of the prop gadget?&amp;quot; The answer is simple: very little. The prop gadget is smart enough to make sure its integer value does not go beyond the bounds of its display. In the example, the prop gadget can only have values from 0 to 90. If the user tries to type a value greater than 90, the prop gadget will set itself to its maximum of 90. Because the integer string gadget doesn&#039;t have any bounds checking built into it, the example needs to find an alternative way to check the bounds.&lt;br /&gt;
&lt;br /&gt;
The other question is, &amp;quot;How does talk2boopsi.c know the current value of the gadgets?&amp;quot; That answer is simple too: it doesn&#039;t. The example doesn&#039;t ask the gadgets what their current values are (which it would do using GetAttr()) and the example doesn&#039;t pay attention to gadget events at the window&#039;s IDCMP port, so it isn&#039;t going to hear about them.&lt;br /&gt;
&lt;br /&gt;
One easy way to hear about changes to the gadget events is to listen for a &amp;quot;release verify&amp;quot;. Conventional Intuition gadgets can trigger a release verify IDCMP event when the user finishes manipulating the gadget. BOOPSI gadgets can do this, too, while continuing to update each other.&lt;br /&gt;
&lt;br /&gt;
To make Talk2boopsi.c do this would require only a few changes. First, the window&#039;s IDCMP port has to be set up to listen for IDCMP_GADGETUP events. Next, the example needs to set the gadget&#039;s GLFG_RELVERIFY flags. It can do this by setting the gadgetclass GA_RelVerify attribute to TRUE for both gadgets. That&#039;s enough to trigger the release verify message, so all Talk2boopsi.c needs to do is account for the new type of IDCMP message, IDCMP_GADGETUP. When Talk2boopsi.c gets a release verify message, it can use GetAttr() to ask the integer gadget its value. If this value is out of range, it should explicitly set the value of the integer gadget to a more suitable value using SetGadgetAttrs().&lt;br /&gt;
&lt;br /&gt;
Using the GLFG_RELVERIFY scheme above, an application will only hear about changes to the gadgets after the user is finished changing them. The application does not hear all of the interim updates that, for example, a prop gadget generates. This is useful if an application only needs to hear the final value and not the interim update.&lt;br /&gt;
&lt;br /&gt;
It is also possible to make the IDCMP port of a BOOPSI gadget&#039;s window the ICA_TARGET of the gadget. There is a special value for ICA_TARGET called ICTARGET_IDCMP (defined in &amp;amp;lt;intuition/icclass.h&amp;amp;gt;). This tells the gadget to send an IDCMP_IDCMPUPDATE class IntuiMessage to its window‚Äôs IDCMP port. Of course, the window has to be set up to listen for IDCMP_IDCMPUPDATE IntuiMessages. The BOOPSI gadget passes an address in the IntuiMessage.IAddress field. It points to an attribute tag list containing the attribute (and its new value) that triggered the IDCMP_IDCMPUPDATE message. An application can use the utility.library tag functions to access the gadget&#039;s attributes in this list. Using this scheme, an application will hear all of the interim gadget updates. If the application is using a gadget that generates a lot of interim OM_UPDATE messages (like a prop gadget), the application should be prepared to handle a lot of messages.&lt;br /&gt;
&lt;br /&gt;
Using this IDCMP_IDCMPUPDATE scheme, if the gadget uses an ICA_MAP to map the attribute to a special dummy attribute ICSPECIAL_CODE (defined in &amp;amp;lt;intuition/icclass.h&amp;amp;gt;), the IntuiMessage.Code field will contain the value of the attribute. Because the attribute&#039;s value is a 32-bit quantity and the IntuiMessage.Code field is only 16 bits wide, only the least significant 16 bits of the attribute will appear in the IntuiMessage.Code field, so it can&#039;t hold a 32-bit quantity, like a pointer. Applications should only use the lower 16 bits of the attribute value.&lt;br /&gt;
&lt;br /&gt;
=== The Interconnection Classes ===&lt;br /&gt;
&lt;br /&gt;
The IDCMP_IDCMPUPDATE scheme presents a problem to an application that wants to make gadgets talk to each other and talk to the application. BOOPSI gadgets only have one ICA_TARGET. One BOOPSI gadget can talk to either another BOOPSI object or its window&#039;s IDCMP port, but not both. Using this scheme alone would force the application to update the integer value of the gadgets, which is what we are trying to avoid in the first place.&lt;br /&gt;
&lt;br /&gt;
One of the standard BOOPSI classes, icclass, is a class of information forwarders. An icclass object receives OM_UPDATE messages from one object and passes those messages on to its own ICA_TARGET. If it needs to map any incoming attributes, it can use its own ICA_MAP to do so.&lt;br /&gt;
&lt;br /&gt;
Icclass has a subclass called &#039;&#039;modelclass&#039;&#039;. Using a modelclass object, an application can chain a series of these objects together to set up a &amp;quot;broadcast list&amp;quot; of icclass objects. The modelclass object is similar to the icclass object in that it has its own ICA_TARGET and ICA_MAP. It differs in that an application can use the modelclass OM_ADDMEMBER method to add icclass objects to the modelclass object&#039;s broadcast list.&lt;br /&gt;
&lt;br /&gt;
The OM_ADDMEMBER method is defined by rootclass. It adds one BOOPSI object to the personal list of another BOOPSI object. It is up to the BOOPSI object&#039;s class to determine the purpose of the objects in the list. Unlike the other methods mentioned so far in this chapter, OM_ADDMEMBER does not have an Intuition function equivalent. To pass an OM_ADDMEMBER message to an object use the function DoMethodA(), or its stack-based equivalent, DoMethod():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG DoMethodA(Object *myobject, Msg boopsimessage);&lt;br /&gt;
ULONG DoMethod(Object *myobject, ULONG methodID, ...);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The return value is class-dependent. The first argument to both of these functions points to the object that will receive the BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
For DoMethodA(), boopsimessage is the actual BOOPSI message. The layout of it depends on the method. Every method&#039;s message starts off with an Msg (from &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
typedef struct {&lt;br /&gt;
    ULONG MethodID; /* Method-specific data may follow this field */&lt;br /&gt;
} *Msg;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The message that the OM_ADDMEMBER method uses looks like this (from &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct opMember {&lt;br /&gt;
    ULONG    MethodID;&lt;br /&gt;
    Object   *opam_Object;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where MethodID is OM_ADDMEMBER and opam_Object points to the object to add to myobject&#039;s list.&lt;br /&gt;
&lt;br /&gt;
DoMethod() uses the stack to build a message. To use DoMethod(), just pass the elements of the method&#039;s message structure as arguments to DoMethod() in the order that they appear in the structure. For example, to ask the BOOPSI object myobject to add the object addobject to its personal list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DoMethod(myobject, OM_ADDMEMBER, addobject);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To rearrange Talk2boopsi.c so that it uses a modelclass object (also known as a model):&lt;br /&gt;
&lt;br /&gt;
* Create the integer and prop gadget.&lt;br /&gt;
* Create the model.&lt;br /&gt;
* Create two icclass objects, one called int2prop and the other called prop2int.&lt;br /&gt;
* Make the model the ICA_TARGET of both the integer gadget and the prop gadget. The gadgets do not need an ICA_MAP.&lt;br /&gt;
* Using DoMethod() to call OM_ADDMEMBER, add the icclass objects to the model&#039;s personal list.&lt;br /&gt;
* Make the prop gadget the ICA_TARGET of int2prop. Make the integer gadget the ICA_TARGET of prop2int.&lt;br /&gt;
* Create an ICA_MAP map list for int2prop that maps STRINGA_LongVal to PGA_Top. Create an ICA_MAP map list for prop2int that maps PGA_Top to STRINGA_LongVal. Make the ICA_TARGET of the model ICTARGET_IDCMP.&lt;br /&gt;
&lt;br /&gt;
Diagrammatically, the new Talk2boopsi.c should look something like this:&lt;br /&gt;
&lt;br /&gt;
Figure 12-4 ICC Diagram&lt;br /&gt;
&lt;br /&gt;
When either of these gadgets has some interim state change (caused by the user manipulating the gadgets), it sends an OM_UPDATE message to its ICA_TARGET, which in this case is the modelclass object. When this model gets the message, it does two things. It sends an IDCMP_IDCMPUPDATE to the IDCMP port of the gadget&#039;s window and it also sends OM_UPDATE messages to all of the objects in its personal list. When int2prop gets an OM_UPDATE message, it forwards that message to its ICA_TARGET, the prop gadget. Similarly, when prop2int gets an OM_UPDATE message, it forwards that message to its ICA_TARGET, the integer gadget.&lt;br /&gt;
&lt;br /&gt;
Although in this case it isn&#039;t a problem, icclass and modelclass objects contain loop inhibition capabilities. If an icclass object (or modelclass object) receives an OM_UPDATE message, it forwards the message to its target. If somehow that forwarded message gets forwarded (or broadcast) back to the icclass object, the icclass object ignores the message. This prevents the possibility of an infinite OM_UPDATE loop.&lt;br /&gt;
&lt;br /&gt;
== Creating a BOOPSI Class ==&lt;br /&gt;
&lt;br /&gt;
So far this chapter has only hinted at what is possible with BOOPSI. Its power lies in its extensibility. BOOPSI grants the application programmer the power to add custom features to existing classes. If an existing class comes close to your needs, you can build on that class so it does exactly what you want. If you want a class that is unlike an existing class, you can create it.&lt;br /&gt;
&lt;br /&gt;
The heart of a BOOPSI class is its method &#039;&#039;Dispatcher&#039;&#039; function. According to the OOP metaphor, when an application wants a BOOPSI object to perform a method, it sends the object a message. In reality, that object is only a data structure, so it does not have the power to do anything. When an object receives a BOOPSI message, a BOOPSI message structure is passed to the dispatcher of that object&#039;s class. The dispatcher examines the message and figures out what to do about it.&lt;br /&gt;
&lt;br /&gt;
For example, when an application calls SetGadgetAttrs() on an integer gadget:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SetGadgetAttrs(myintegergadget, mywindow, NULL,&lt;br /&gt;
               STRINGA_LongVal, 75L,&lt;br /&gt;
               GA_ID,           2L,&lt;br /&gt;
               TAG_END));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the SetGadgetAttrs() function calls the strgclass dispatcher. A BOOPSI dispatcher receives three arguments: a pointer to the dispatcher&#039;s Class (defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;), a pointer to the object that is going to perform the method, and a pointer to the BOOPSI message. In this case, the SetGadgetAttrs() function builds an OM_SET message, finds the strgclass dispatcher, and &amp;quot;sends&amp;quot; the dispatcher the OM_SET message. SetGadgetAttrs() can find the dispatcher because an object contains a reference to its dispatcher.&lt;br /&gt;
&lt;br /&gt;
When the dispatcher function &amp;quot;gets&amp;quot; the message, it examines the message to find out its corresponding method. In this case, the dispatcher recognizes the message as an OM_SET message and proceeds to set myintegergadget&#039;s attributes.&lt;br /&gt;
&lt;br /&gt;
An OM_SET message looks like this (defined in &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct opSet {&lt;br /&gt;
    ULONG MethodID;                   /* This will be set to OM_SET      */&lt;br /&gt;
    struct TagItem    *ops_AttrList;  /* A tag list containing the       */&lt;br /&gt;
                                      /*     attribute/value pairs of    */&lt;br /&gt;
                                      /*     the attributes to set.      */&lt;br /&gt;
    struct GadgetInfo *ops_GInfo;     /* Special information for gadgets */&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The OM_SET message contains a pointer to a tag list in ops_AttrList that looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{STRINGA_LongVal, 75L},&lt;br /&gt;
{GA_ID, 2L},&lt;br /&gt;
{TAG_END,}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The strgclass dispatcher scans through this tag list and recognizes the STRINGA_LongVal attribute. The dispatcher sets myintegergadget&#039;s internal STRINGA_LongVal value to the corresponding value (75L) from the attribute/value pair.&lt;br /&gt;
&lt;br /&gt;
The strgclass dispatcher continues to scan through the tag list. When it finds GA_ID, it does not process it like STRINGA_LongVal. The strgclass dispatcher&#039;s OM_SET method does not recognize the GA_ID attribute because strgclass &#039;&#039;inherited&#039;&#039; the GA_ID attribute from gadgetclass. To handle setting the GA_ID attribute, the strgclass dispatcher passes on the OM_SET message to its superclass&#039;s dispatcher. The strgclass dispatcher passes control to the gadgetclass dispatcher, which knows about the GA_ID attribute.&lt;br /&gt;
&lt;br /&gt;
=== Building on Existing Public Classes ===&lt;br /&gt;
&lt;br /&gt;
A program can create its own subclasses which build on the features of existing classes. For example, a program could create a subclass of modelclass named rkmmodelclass. Rkmmodelclass builds on modelclass by adding a new attribute called RKMMOD_CurrVal. This purpose of this attribute is simply to hold an integer value.&lt;br /&gt;
&lt;br /&gt;
Because this new attribute is built into an rkmmodel object, the object could be implemented so that it exercises a certain amount of control over that value. For example, rkmmodelclass could be implemented so an rkmmodel performs bounds checking on its internal value. When an application asks an rkmmodel to set its internal RKMMOD_CurrVal, the rkmmodel makes sure the new value is not beyond a maximum value. If the new value is beyond the maximum, it sets its current value to the maximum. After the rkmmodelclass object has set its internal RKMMOD_CurrVal, it can broadcast the change on to objects in its broadcast list.&lt;br /&gt;
&lt;br /&gt;
The dispatcher for rkmmodelclass does not have to do a lot of work because it inherits most of its behavior from its superclasses. The rkmmodelclass has to take care of setting aside memory for the RKMMOD_CurrVal attribute and processing any OM_SET requests to set the RKMMOD_CurrVal attribute. For any other attributes or methods, the rkmmodelclass dispatcher passes on processing to its superclass, modelclass.&lt;br /&gt;
&lt;br /&gt;
==== Building Rkmmodelclass ====&lt;br /&gt;
&lt;br /&gt;
So far, the theoretical class rkmmodelclass has just one attribute, RKMMOD_CurrVal. A couple of extra attributes can make it more useful. Because the rkmmodel object maintains an upper limit on its RKMMOD_CurrVal integer value, it would be useful if that upper limit was variable. Using a new attribute, RKMMOD_Limit, an application can tell a rkmmodel what its upper limit is. The rkmmodel will enforce the limit internally, so the application doesn‚Äôt have to worry about it.&lt;br /&gt;
&lt;br /&gt;
Another useful addition is a pulse increment and decrement for RKMMOD_CurrVal. Whenever the model receives an increment or decrement command, it increments or decrements its internal value. To make the example class simple, rkmmodelclass implements incrementing and decrementing by creating &amp;quot;dummy&amp;quot; attributes called RKMMOD_Up and RKMMOD_Down. When an rkmmodel receives an OM_SET message for one of these attributes, it increments or decrements RKMMOD_CurrVal. An rkmmodelclass object does not care what the value of the RKMMOD_Up and RKMMOD_Down attributes are, it only cares that it received an OM_UPDATE about it.&lt;br /&gt;
&lt;br /&gt;
There are two pieces of data that make up this new class&#039;s instance data: the rkmmodel&#039;s current value (RKMMOD_CurrVal) and the upper limit of the rkmmodel (RKMMOD_Limit). The example class consolidates them into one structure:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct RKMModData {&lt;br /&gt;
    ULONG currval;&lt;br /&gt;
    ULONG vallimit;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Writing the Dispatcher ===&lt;br /&gt;
&lt;br /&gt;
The C prototype for a BOOPSI dispatcher looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG dispatchRKMModel(Class *cl, Object *recvobject, Msg msg);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where cl points to the Class (defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;) of the dispatcher, recvobject points to the object that received the message, and msg is that BOOPSI message. The format of the message varies according to the method. The default BOOPSI message is an Msg (from &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
typedef struct {&lt;br /&gt;
    ULONG MethodID;&lt;br /&gt;
} *Msg;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
BOOPSI methods that require parameters use custom message structures. The first field of any message structure is always the method&#039;s methodID. This makes custom messages look like an Msg. The dispatcher looks at an incoming message&#039;s first field to tell what its method is. Rkmmodelclass objects respond to several rootclass methods:&lt;br /&gt;
&lt;br /&gt;
; OM_NEW&lt;br /&gt;
: This method creates a new rkmmodelclass object. It uses an opSet structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_DISPOSE&lt;br /&gt;
: This method tells an object to dispose of itself. It uses an Msg as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_SET&lt;br /&gt;
: This method tells an object to set one or more of its attribute values. It uses an opSet structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_UPDATE&lt;br /&gt;
: This method tells an object to update one or more of its attribute values. It uses an opUpdate structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_GET&lt;br /&gt;
: This method tells an object to report an attribute value. It uses an opGet structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_ADDTAIL&lt;br /&gt;
: This method tells an object to add itself to the end of an Exec list. It uses an opAddTail structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_REMOVE&lt;br /&gt;
: This method tells an object to remove itself from an Exec list. It uses an Msg as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_ADDMEMBER&lt;br /&gt;
: This method tells an object to add an object to its broadcast list. It uses an opMember structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_REMMEMBER&lt;br /&gt;
: This method tells an object to remove an object from its broadcast list. It uses an opMember structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
; OM_NOTIFY&lt;br /&gt;
: This method tells an object to broadcast an attribute change to its broadcast list. It uses an opSet structure as its BOOPSI message.&lt;br /&gt;
&lt;br /&gt;
Of these, rkmmodelclass has to process OM_NEW, OM_SET, OM_UPDATE, and OM_GET.&lt;br /&gt;
&lt;br /&gt;
==== OM_NEW ====&lt;br /&gt;
&lt;br /&gt;
The OM_NEW method returns a pointer to a newly created BOOPSI object, or NULL if it failed to create the object. This method receives the following message structure (defined in &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct opSet {  /* The OM_NEW method uses the same structure as OM_GET */&lt;br /&gt;
    ULONG             MethodID;&lt;br /&gt;
    struct TagItem    *ops_AttrList;&lt;br /&gt;
    struct GadgetInfo *ops_GInfo;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The ops_AttrList field contains a pointer to a TagItem array of attribute/value pairs. These contain the initial values of the new object&#039;s attributes. The ops_GInfo field is always NULL for the OM_NEW method.&lt;br /&gt;
&lt;br /&gt;
Unlike other methods, when a dispatcher gets an OM_NEW message, the object pointer (recvobject from the dispatchRKMModel() prototype above) does not point to an object. It doesn&#039;t make sense for recvobject to point to an object because the idea is to create a new object, not act on an existing one.&lt;br /&gt;
&lt;br /&gt;
The pointer normally used to pass a BOOPSI object is instead used to pass the address of the object&#039;s &amp;quot;true class&amp;quot;. An object&#039;s true class is the class of which the object is an instance.&lt;br /&gt;
&lt;br /&gt;
The first thing the dispatcher does when it processes an OM_NEW message is pass the OM_NEW message on to its superclass&#039;s dispatcher. It does this using function DoSuperMethodA():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ULONG DoSuperMethodA(Class *cl, Object *trueclass, Msg msg);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each dispatcher passes control to its superclass. Eventually the message will arrive at the rootclass dispatcher. The OM_NEW method in the rootclass dispatcher looks at the object&#039;s true class (trueclass from the prototype) to find out which class dispatcher is trying to create a new object. Note that trueclass is not necessarily the same as the current dispatcher&#039;s class (cl from the dispatchRKMModel() prototype above), although this would be the case if the object&#039;s true class is a subclass of the current dispatcher&#039;s class.&lt;br /&gt;
&lt;br /&gt;
The rootclass dispatcher uses the true class to find out how much memory to allocate for the object&#039;s instance data. Each class keeps a record of how much memory its local instance data requires. The rootclass dispatcher also looks at each class between the true class and rootclass to find out much memory the local instance data for those classes require. The rootclass dispatcher totals the amount of local instance data memory needed by the true class and each of its superclasses and allocates that much memory.&lt;br /&gt;
&lt;br /&gt;
If all goes well, the rootclass dispatcher increments a private field in the true class that keeps track of how many instances of the true class there currently are. It then returns a pointer to the newly created object and passes control back to the subclass dispatcher that called it, which is icclass in the case of rkmmodelclass. If there was a problem, the rootclass dispatcher does not increment the object count and passes back a NULL.&lt;br /&gt;
&lt;br /&gt;
When the rootclass dispatcher returns, the &#039;&#039;icclass dispatcher&#039;&#039; regains control from DoSuperMethodA(). DoSuperMethodA() will return either a pointer to the new object or else it returns NULL if there was an error. Although the rootclass dispatcher allocated all the memory the object needs, it only initialized the instance data local to rootclass. Now it&#039;s the icclass dispatcher&#039;s turn to do some work. It has to initialize the instance data that is local to icclass.&lt;br /&gt;
&lt;br /&gt;
A dispatcher finds its local instance data by using the INST_DATA() macro (defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
VOID * INST_DATA(Class *localclass, Object *object);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
INST_DATA() takes two arguments, a pointer to a class and a pointer to the object. The INST_DATA() macro returns a pointer to the instance data local to localclass. When the icclass dispatcher was called, it received three arguments, one of which was a pointer to the local class (icclass). The icclass dispatcher passes this pointer and the new object pointer it got from DoSuperMethodA() to INST_DATA() to get a pointer to the instance data local to icclass.&lt;br /&gt;
&lt;br /&gt;
After initializing its local instance data, the icclass dispatcher passes control back to the modelclass dispatcher, which in turn, initializes the instance data local to modelclass. Finally, the rkmmodelclass dispatcher regains control and now has to take care of its local instance data.&lt;br /&gt;
&lt;br /&gt;
To find its local instance data, the rkmmodelclass dispatcher needs a pointer to its Class and a pointer to the new object. The dispatcher function gets its Class pointer as its first argument (cl from the dispatchRKMModel() prototype above). It gets the new object pointer as the return value from DoSuperMethodA(). In this case, INST_DATA() returns a pointer to an RKMModData structure.&lt;br /&gt;
&lt;br /&gt;
Now the dispatcher has to initialize its local instance data. It has to scan through the tag list passed in the OM_NEW message looking for initial values for the RKMMOD_CurrVal and RKMMOD_Limit attributes. As an alternative, the dispatcher&#039;s OM_NEW method can use its OM_SET method to handle initializing these &amp;quot;settable&amp;quot; attributes.&lt;br /&gt;
&lt;br /&gt;
Finally, the dispatcher can return. When the dispatcher returns from an OM_NEW method, it returns a pointer to the new object.&lt;br /&gt;
&lt;br /&gt;
If the OM_NEW method fails, it should tell the partially initialized object it got from its superclass&#039;s dispatcher to dispose of itself (using OM_DISPOSE) and return NULL.&lt;br /&gt;
&lt;br /&gt;
==== OM_SET/OM_UPDATE ====&lt;br /&gt;
&lt;br /&gt;
For the OM_SET message, the rkmmodelclass dispatcher steps through the attribute/value pairs passed to it in the OM_SET message looking for the local attributes (see OM_NEW for the OM_SET message structure). The RKMMOD_Limit attribute is easy to process. Just find it and record the value in the local RKMModData.vallimit field.&lt;br /&gt;
&lt;br /&gt;
Because the function of the rkmmodelclass&#039;s OM_SET and OM_UPDATE methods are almost identical, the rkmmodelclass dispatcher handles them as the same case. The only difference is that, because the OM_UPDATE message comes from another BOOPSI object, the OM_UPDATE method can report on transitory state changes of an attribute. For example, when the user slides a BOOPSI prop gadget, that prop gadget sends out an interim OM_UPDATE message for every interim value of PGA_Top. When the user lets go of the prop gadget, the gadget sends out a final OM_UPDATE message. The OM_UPDATE message is almost identical to the OM_SET message:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#define OPUF_INTERIM    (1&amp;amp;lt;&amp;amp;lt;0)&lt;br /&gt;
struct opUpdate {                     /* the OM_NOTIFY method uses the same structure */&lt;br /&gt;
    ULONG             MethodID;&lt;br /&gt;
    struct TagItem    *opu_AttrList;&lt;br /&gt;
    struct GadgetInfo *opu_GInfo;&lt;br /&gt;
    ULONG             opu_Flags;      /* The extra field */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A dispatcher can tell the difference between an interim and final OM_UPDATE message because the OM_UPDATE message has an extra field on it for flags. If the low order bit (the OPUF_INTERIM bit) is set, this is an interim OM_UPDATE message. The interim flag is useful to a class that wants to ignore any transitory messages, processing only final attribute values. Because rkmmodelclass wants to process all changes to its attributes, it processes all OM_UPDATE messages.&lt;br /&gt;
&lt;br /&gt;
The RKMMOD_CurrVal attribute is a little more complicated to process. The dispatcher has to make sure the new current value is within the limits set by RKMMOD_Limit, then record that new value in the local RKMModData.currval field. Because other objects need to hear about changes to RKMMOD_CurrVal, the dispatcher has to send a &#039;&#039;notification&#039;&#039; request. It does this by sending itself an OM_NOTIFY message. The OM_NOTIFY message tells an object to notify its targets (its ICA_TARGET and the objects in its broadcast list) about an attribute change. The OM_NOTIFY method does this by sending OM_UPDATE messages to all of an object&#039;s targets.&lt;br /&gt;
&lt;br /&gt;
The rkmmodelclass dispatcher does not handle the OM_NOTIFY message itself. It inherits this method from modelclass, so the rkmmodelclass dispatcher passes OM_NOTIFY messages on to its superclass.&lt;br /&gt;
&lt;br /&gt;
To notify its targets, the rkmmodelclass dispatcher has to construct an OM_NOTIFY message. The OM_NOTIFY method uses the same message structure as OM_UPDATE. Using the stack-based version of DoSuperMethodA(), DoSuperMethod(), the dispatcher can build an OM_NOTIFY message on the stack:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 . . .&lt;br /&gt;
&lt;br /&gt;
struct TagItem tt[2];&lt;br /&gt;
struct opUpdate *msg;&lt;br /&gt;
 . . .&lt;br /&gt;
&lt;br /&gt;
tt[0].ti_Tag  = RKMMOD_CurrVal;  /* make a tag list.  */&lt;br /&gt;
tt[0].ti_Data = mmd-&amp;amp;gt;currval;&lt;br /&gt;
tt[1].ti_Tag  = TAG_END;&lt;br /&gt;
&lt;br /&gt;
DoSuperMethod(cl, o, OM_NOTIFY, tt, msg-&amp;amp;gt;opu__GInfo,&lt;br /&gt;
              ((msg-&amp;amp;gt;MethodID == OM_UPDATE) ? (msg-&amp;amp;gt;opu_Flags) : 0L));&lt;br /&gt;
 . . .&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because the OM_NOTIFY needs a tag list of attributes about which to issue updates, the dispatcher builds a tag list containing just the RKMMOD_CurrVal tag and its new value. The dispatcher doesn&#039;t use the tag list passed to it in the OM_UPDATE/OM_NOTIFY message because that list can contain many other attributes besides RKMMOD_CurrVal.&lt;br /&gt;
&lt;br /&gt;
The msg variable in the DoSuperMethod() call above is the OM_SET or OM_UPDATE message that was passed to the dispatcher. The dispatcher uses that structure to find a pointer to the GadgetInfo structure that the OM_NOTIFY message requires. The GadgetInfo structure comes from Intuition and contains information that BOOPSI gadgets need to render themselves. For the moment, don&#039;t worry about what the GadgetInfo structure actually does, just pass it on. The targets of an rkmmodel will probably need it.&lt;br /&gt;
&lt;br /&gt;
Notice that the dispatcher has to test to see if the message is an OM_SET or OM_UPDATE so it can account for the opu_Flags field at the end of the OM_UPDATE message.&lt;br /&gt;
&lt;br /&gt;
Processing the RKMMOD_Up and RKMMOD_Down attributes is similar to the RKMMOD_CurrVal attribute. When the dispatcher sees one of these, it has to increment or decrement the local RKMModData.currval, making sure RKMModData.currval is within limits. The dispatcher then sends an OM_NOTIFY message to the superclass about the change to RKMModData.currval.&lt;br /&gt;
&lt;br /&gt;
The return value from the dispatcher&#039;s OM_SET method depends on the what effect the attribute change has to the visual state of the objects in the rkmmodel&#039;s broadcast list. If an attribute change will not affect the visual state of the rkmmodel&#039;s objects, the OM_SET method returns zero. If the attribute change could trigger a change to the rkmmodel&#039;s objects, it returns something besides zero. For example, the rkmmodelclass OM_SET method returns 1L if an rkmmodel&#039;s RKMMOD_CurrVal, RKMMOD_Up, or RKMMOD_Down attribute is changed.&lt;br /&gt;
&lt;br /&gt;
At some point the rkmmodelclass dispatcher has to allow its superclasses to process these attributes it inherits. Normally a dispatcher lets the superclass process its attributes before attempting to process any local attributes. The rkmmodelclass dispatcher does this by passing on the OM_SET or OM_UPDATE message using DoSuperMethodA() (inheritance at work!). As an alternative, the dispatcher can use the &#039;&#039;amiga.lib&#039;&#039; function SetSuperAttrs(). See the &#039;&#039;amiga.lib&#039;&#039; Autodocs for more details on this function.&lt;br /&gt;
&lt;br /&gt;
==== OM_GET ====&lt;br /&gt;
&lt;br /&gt;
The rkmmodel only has one &amp;quot;gettable&amp;quot; attribute: RKMMOD_CurrVal, which makes processing it easy. The OM_GET message looks like this (defined in &amp;amp;lt;intuition/classusr.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct opGet {&lt;br /&gt;
    ULONG MethodID;     /* OM_GET */&lt;br /&gt;
    ULONG opg_AttrID;   /* The attribute to retrieve */&lt;br /&gt;
    ULONG *opg_Storage; /* a place to put the attribute&#039;s value */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the rkmmodelclass dispatcher receives an OM_GET message with an opg_AttrID equal to RKMMOD_CurrVal, it copies the current value (RKMModData.currval) to the memory location opg_Storage points to and returns a value of TRUE. The TRUE indicates that there was no error. If opg_AttrID is not RKMMOD_CurrVal, the dispatcher should let its superclass handle this message.&lt;br /&gt;
&lt;br /&gt;
The rkmmodelclass dispatcher can take advantage of the fact that the only &amp;quot;gettable&amp;quot; attribute available to an rkmmodel is RKMMOD_CurrVal (the attributes defined by modelclass and icclass are not gettable-see the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual for more details on which attributes are &amp;quot;settable&amp;quot;, &amp;quot;gettable&amp;quot;, etc.). If opg_AttrID is not RKMMOD_CurrVal, the rkmmodelclass dispatcher can return FALSE, indicating that the attribute was not &amp;quot;gettable&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
If the rkmmodelclass dispatcher comes across any other messages besides OM_NEW, OM_SET, OM_UPDATE, and OM_GET message, it blindly passes them on to its superclass for processing.&lt;br /&gt;
&lt;br /&gt;
==== Making the New Class ====&lt;br /&gt;
&lt;br /&gt;
The Intuition function MakeClass() creates a new BOOPSI class:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Class *MakeClass(UBYTE *newclassID, UBYTE *pubsuperclassID, Class *privsuperclass,&lt;br /&gt;
                 UWORD instancesize, ULONG flags);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the new class is going to be public, newclassID is a string naming the new class. If the new class is private, this field is NULL. The next two fields tell MakeClass() where to find the new class&#039;s superclass. If the superclass is public, pubsuperclassID points to a string naming that public superclass and the privsuperclass pointer is NULL. If the superclass is private, privsuperclass points to that superclass&#039;s Class structure and pubsuperclassID is NULL. The size of the new class&#039;s local instance data is instancesize. The last parameter, flags, is for future enhancement. For now, make this zero.&lt;br /&gt;
&lt;br /&gt;
If it is successful, MakeClass() returns a pointer to the new class, otherwise it returns NULL. When MakeClass() is successful, it also takes measures to make sure no one can &amp;quot;close&amp;quot; the new class&#039;s superclass (using FreeClass()). It does this by incrementing a private field of the superclass that keeps track of how many subclasses the superclass currently has.&lt;br /&gt;
&lt;br /&gt;
After successfully creating a class, an application has to tell the class where its dispatcher is. The Class pointer (defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;) returned by MakeClass() contains a Hook structure called cl_Dispatcher, which is used to call the dispatcher. The application has to initialize this hook:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
myclass-&amp;amp;gt;cl_Dispatcher.h_Entry = HookEntry; /* &amp;amp;lt;--- HookEntry() is defined in amiga.lib */&lt;br /&gt;
myclass-&amp;amp;gt;cl_Dispatcher.h_SubEntry = dispatchRKMModel;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The h_Entry field points to a function that copies the function arguments to where the dispatcher expects them. See the &#039;&#039;Callback Hooks&#039;&#039; section of the [[Utility_Library|Utility Library]] for more details.&lt;br /&gt;
&lt;br /&gt;
To make a class public instead of private, an application has to call AddClass() in addition to giving the class a name in MakeClass(). AddClass() takes one argument, a pointer to a valid Class structure that has been initialized as a public class by MakeClass(). To remove a public class added to the system with AddClass(), pass the public class pointer to RemoveClass(). See the Intuition Autodocs for more details on AddClass() and RemoveClass().&lt;br /&gt;
&lt;br /&gt;
==== RKMModel.c ====&lt;br /&gt;
&lt;br /&gt;
The following code, RKMModel.c, makes up an initialization function and the dispatcher function for a private class informally called rkmmodelclass.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* RKMModel.c - A simple custom modelclass subclass.&lt;br /&gt;
LC -cfist -b1 -y -v -j73 rkmmodel.c&lt;br /&gt;
quit ;*/&lt;br /&gt;
&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/intuition.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/classes.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/classusr.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/imageclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/cghooks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/icclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/tagitem.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/hooks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/intuition_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/utility_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/alib_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/alib_stdio_protos.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
extern struct Library *IntuitionBase, *UtilityBase;&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/****************  The attributes defined by this class  *****************************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
#define RKMMOD_CurrVal  (TAG_USER + 1) /* This attribute is the current value of the model.*******/&lt;br /&gt;
                                       /**********************************************************/&lt;br /&gt;
#define RKMMOD_Up       (TAG_USER + 2) /* These two are fake attributes that rkmmodelclass *******/&lt;br /&gt;
#define RKMMOD_Down     (TAG_USER + 3) /* uses as pulse values to increment/decrement the  *******/&lt;br /&gt;
                                       /* rkmmodel&#039;s RKMMOD_CurrVal attribute.             *******/&lt;br /&gt;
                                       /**********************************************************/&lt;br /&gt;
#define RKMMOD_Limit    (TAG_USER + 4) /* This attribute contains the upper bound of the   *******/&lt;br /&gt;
                                       /* rkmmodel&#039;s RKMMOD_CurrVal.  The rkmmodel has a   *******/&lt;br /&gt;
                                       /* static lower bound of zero.                      *******/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
&lt;br /&gt;
#define DEFAULTVALLIMIT 100L /* If the programmer doesn&#039;t set               */&lt;br /&gt;
                             /* RKMMOD_Limit, it defaults to this.          */&lt;br /&gt;
struct RKMModData {&lt;br /&gt;
    ULONG currval;       /* The instance data for this class.               */&lt;br /&gt;
    ULONG vallimit;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/**************************      The functions in this module     ********************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
void    geta4(void);                                                              /***************/&lt;br /&gt;
Class  *initRKMModClass(void);                                                    /***************/&lt;br /&gt;
BOOL    freeRKMModClass(Class *);                                                 /***************/&lt;br /&gt;
ULONG   dispatchRKMModel(Class *, Object *, Msg);                                 /***************/&lt;br /&gt;
void    NotifyCurrVal(Class *, Object *, struct opUpdate *, struct RKMModData *); /***************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/********************************   Initialize the class    **************************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
Class   *initRKMModClass(void)                /* Make the class and set     */&lt;br /&gt;
{                                             /* up the dispatcher&#039;s hook.  */&lt;br /&gt;
    Class *cl;&lt;br /&gt;
    extern ULONG HookEntry();  /*      &amp;amp;lt;-------   defined in amiga.lib.     */&lt;br /&gt;
&lt;br /&gt;
    if ( cl =  MakeClass( NULL,&lt;br /&gt;
                &amp;amp;quot;modelclass&amp;amp;quot;, NULL,&lt;br /&gt;
                sizeof ( struct RKMModData ),&lt;br /&gt;
                0 ))&lt;br /&gt;
    {&lt;br /&gt;
        cl-&amp;amp;gt;cl_Dispatcher.h_Entry = HookEntry;           /* initialize the  */&lt;br /&gt;
        cl-&amp;amp;gt;cl_Dispatcher.h_SubEntry = dispatchRKMModel; /* cl_Dispatcher   */&lt;br /&gt;
                                                         /* Hook.           */&lt;br /&gt;
    }&lt;br /&gt;
    return ( cl );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/*********************************      Free the class     ***************************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
BOOL freeRKMModClass( Class *cl )&lt;br /&gt;
{&lt;br /&gt;
    return (FreeClass(cl));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/********************************     The class Dispatcher     ***********************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
ULONG dispatchRKMModel(Class *cl, Object *o, Msg msg)&lt;br /&gt;
{&lt;br /&gt;
    struct RKMModData *mmd;&lt;br /&gt;
    APTR retval = NULL;  /* A generic return value used by this class&#039;s methods.  The            */&lt;br /&gt;
                         /* meaning of this field depends on the method.  For example,           */&lt;br /&gt;
                         /* OM_GET uses this a a boolean return value, while OM_NEW              */&lt;br /&gt;
                         /* uses it as a pointer to the new object.                              */&lt;br /&gt;
    geta4();    /* SAS/C and Manx function - makes sure A4 contains global data pointer.         */&lt;br /&gt;
&lt;br /&gt;
    switch (msg-&amp;amp;gt;MethodID)&lt;br /&gt;
    {&lt;br /&gt;
        case OM_NEW:     /* Pass message onto superclass first so it can set aside the memory    */&lt;br /&gt;
                         /* for the object and take care of superclass instance data.            */&lt;br /&gt;
            if (retval = (APTR)DoSuperMethodA(cl, o, msg))&lt;br /&gt;
            {            /************************************************************************/&lt;br /&gt;
                         /* For the OM_NEW method, the object pointer passed to the dispatcher   */&lt;br /&gt;
                         /* does not point to an object (how could it? The object doesn&#039;t exist  */&lt;br /&gt;
                         /* yet.)  DoSuperMethodA() returns a pointer to a newly created         */&lt;br /&gt;
                         /* object. INST_DATA() is a macro defined in &amp;amp;lt;intuition/classes.h&amp;amp;gt;      */&lt;br /&gt;
                         /* that returns a pointer to the object&#039;s instance data that is local   */&lt;br /&gt;
                         /* to this class. For example, the instance data local to this class    */&lt;br /&gt;
                         /* is the RKMModData structure defined above.                           */&lt;br /&gt;
                         /************************************************************************/&lt;br /&gt;
                mmd = INST_DATA(cl, retval);&lt;br /&gt;
                mmd-&amp;amp;gt;currval = GetTagData(RKMMOD_CurrVal, 0L, /* initialize object&#039;s attributes. */&lt;br /&gt;
                                          ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList);&lt;br /&gt;
                mmd-&amp;amp;gt;vallimit = GetTagData(RKMMOD_Limit, DEFAULTVALLIMIT,&lt;br /&gt;
                                           ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList);&lt;br /&gt;
            }&lt;br /&gt;
            break;&lt;br /&gt;
        case OM_SET:&lt;br /&gt;
        case OM_UPDATE:&lt;br /&gt;
            mmd = INST_DATA(cl, o);&lt;br /&gt;
            DoSuperMethodA(cl, o, msg);   /* Let the superclasses set their attributes first.    */&lt;br /&gt;
            {&lt;br /&gt;
                struct TagItem *tstate, *ti;    /* grab some temp variables off of the stack.    */&lt;br /&gt;
&lt;br /&gt;
                ti = ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList;&lt;br /&gt;
                tstate = ti;&lt;br /&gt;
&lt;br /&gt;
                        /* Step through all of the attribute/value pairs in the list.  Use the   */&lt;br /&gt;
                        /* utility.library tag functions to do this so they can properly process */&lt;br /&gt;
                        /* special tag IDs like TAG_SKIP, TAG_IGNORE, etc.                       */&lt;br /&gt;
&lt;br /&gt;
                while (ti = NextTagItem(&amp;amp;amp;tstate))    /* Step through all of the attribute/value  */&lt;br /&gt;
                {                 /* pairs in the list. Use the utility.library tag functions    */&lt;br /&gt;
                                  /* to do this so they can properly process special tag IDs     */&lt;br /&gt;
                                  /* like TAG_SKIP, TAG_IGNORE, etc.                             */&lt;br /&gt;
                    switch (ti-&amp;amp;gt;ti_Tag)&lt;br /&gt;
                    {&lt;br /&gt;
                        case RKMMOD_CurrVal:&lt;br /&gt;
                            if ((ti-&amp;amp;gt;ti_Data) &amp;amp;gt; mmd-&amp;amp;gt;vallimit) ti-&amp;amp;gt;ti_Data =&lt;br /&gt;
                                    mmd-&amp;amp;gt;vallimit;&lt;br /&gt;
                            mmd-&amp;amp;gt;currval = ti-&amp;amp;gt;ti_Data;&lt;br /&gt;
                            NotifyCurrVal(cl, o, msg, mmd);&lt;br /&gt;
                            retval = (APTR)1L;  /* Changing RKMMOD_CurrVal can cause a visual    */&lt;br /&gt;
                            break;              /* change to gadgets in the rkmmodel&#039;s broadcast */&lt;br /&gt;
                                                /* list.  The rkmmodel has to tell the applica-  */&lt;br /&gt;
                                                /* tion by returning a value besides zero.       */&lt;br /&gt;
                        case RKMMOD_Up:&lt;br /&gt;
                            mmd-&amp;amp;gt;currval++;&lt;br /&gt;
&lt;br /&gt;
                                 /* Make sure the current value is not greater than value limit. */&lt;br /&gt;
                            if ((mmd-&amp;amp;gt;currval) &amp;amp;gt; mmd-&amp;amp;gt;vallimit) mmd-&amp;amp;gt;currval = mmd-&amp;amp;gt;vallimit;&lt;br /&gt;
                            NotifyCurrVal(cl, o, msg, mmd);&lt;br /&gt;
                            retval = (APTR)1L;  /* Changing RKMMOD_Up can cause a visual         */&lt;br /&gt;
                            break;              /* change to gadgets in the rkmmodel&#039;s broadcast */&lt;br /&gt;
                                                /* list.  The rkmmodel has to tell the applica-  */&lt;br /&gt;
                                                /* tion by returning a value besides zero.       */&lt;br /&gt;
                        case RKMMOD_Down:&lt;br /&gt;
                            mmd-&amp;amp;gt;currval--;&lt;br /&gt;
                                    /* Make sure currval didn&#039;t go negative. */&lt;br /&gt;
                            if ((LONG)(mmd-&amp;amp;gt;currval) == -1L)&lt;br /&gt;
                                mmd-&amp;amp;gt;currval = 0L;&lt;br /&gt;
                            NotifyCurrVal(cl, o, msg, mmd);&lt;br /&gt;
                            retval = (APTR)1L;  /* Changing RKMMOD_Down can cause a visual       */&lt;br /&gt;
                            break;              /* change to gadgets in the rkmmodel&#039;s broadcast */&lt;br /&gt;
                                                /* list.  The rkmmodel has to tell the applica-  */&lt;br /&gt;
                                                /* tion by returning a value besides zero.       */&lt;br /&gt;
                        case RKMMOD_Limit:&lt;br /&gt;
                            mmd-&amp;amp;gt;vallimit = ti-&amp;amp;gt;ti_Data; /* Set the limit.  Note that this does  */&lt;br /&gt;
                            break;                       /* not do bounds checking on the        */&lt;br /&gt;
                                                         /* current RKMModData.currval value.    */&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            break;&lt;br /&gt;
        case OM_GET:                     /* The only attribute that is &amp;amp;quot;gettable&amp;amp;quot; in this        */&lt;br /&gt;
            mmd = INST_DATA(cl, o);      /* class or its superclasses is RKMMOD_CurrVal.         */&lt;br /&gt;
            if ((((struct opGet *)msg)-&amp;amp;gt;opg_AttrID) == RKMMOD_CurrVal)&lt;br /&gt;
            {&lt;br /&gt;
                *(((struct opGet *)msg)-&amp;amp;gt;opg_Storage) = mmd-&amp;amp;gt;currval;&lt;br /&gt;
                retval = (Object *)TRUE;&lt;br /&gt;
            }&lt;br /&gt;
            else retval = (APTR)DoSuperMethodA(cl, o, msg);&lt;br /&gt;
            break;&lt;br /&gt;
        default:       /* rkmmodelclass does not recognize the methodID, so let the superclass&#039;s */&lt;br /&gt;
                       /* dispatcher take a look at it.                                          */&lt;br /&gt;
            retval = (APTR)DoSuperMethodA(cl, o, msg);&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
    return((ULONG)retval);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
void NotifyCurrVal(Class *cl, Object *o, struct opUpdate *msg, struct RKMModData *mmd)&lt;br /&gt;
{&lt;br /&gt;
    struct TagItem tt[2];&lt;br /&gt;
&lt;br /&gt;
    tt[0].ti_Tag = RKMMOD_CurrVal; /* make a tag list.  */&lt;br /&gt;
    tt[0].ti_Data = mmd-&amp;amp;gt;currval;&lt;br /&gt;
    tt[1].ti_Tag = TAG_DONE;&lt;br /&gt;
                                /* If the RKMMOD_CurrVal changes, we want everyone to know about */&lt;br /&gt;
    DoSuperMethod(cl, o,        /* it. Theoretically, the class is supposed to send itself a     */&lt;br /&gt;
             OM_NOTIFY,         /* OM_NOTIFY message. Because this class lets its superclass     */&lt;br /&gt;
             tt,                /* handle the OM_NOTIFY message, it skips the middleman and      */&lt;br /&gt;
             msg-&amp;amp;gt;opu_GInfo,    /* sends the OM_NOTIFY directly to its superclass.               */&lt;br /&gt;
&lt;br /&gt;
             ((msg-&amp;amp;gt;MethodID == OM_UPDATE) ? (msg-&amp;amp;gt;opu_Flags) : 0L)); /* If this is an OM_UPDATE */&lt;br /&gt;
                                /* method, make sure the part the OM_UPDATE message adds to the  */&lt;br /&gt;
                                /* OM_SET message gets added.  That lets the dispatcher handle   */&lt;br /&gt;
}                               /* OM_UPDATE and OM_SET in the same case.                        */&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Below is a diagram showing how an application could use an rkmmodelclass object:&lt;br /&gt;
&lt;br /&gt;
Figure 12-5 Rkmmodelclass Object Diagram&lt;br /&gt;
&lt;br /&gt;
In this diagram, the application uses buttongclass BOOPSI gadgets to send the rkmmodelclass the RKMMOD_Up and RKMMOD_Down attribute pulses.&lt;br /&gt;
&lt;br /&gt;
The example takes advantage of an odd feature of buttongclass. When the user clicks on a buttongclass gadget, it sends an OM_UPDATE to its ICA_TARGET, even though no BOOPSI attribute of buttongclass has changed. It does this because it&#039;s a convenient way to report button clicks.&lt;br /&gt;
&lt;br /&gt;
Whenever a gadget sends a notification, the list of attribute/value pairs in the OM_NOTIFY message always contains the gadget&#039;s GA_ID. This is an easy way for the button to inform its target of its ID so the target knows which gadget sent the OM_UPDATE message. When a buttongclass sends a notification because of a button click, it only sends out an OM_UPDATE about its GA_ID because none of its attributes changed.&lt;br /&gt;
&lt;br /&gt;
When the user clicks one of the buttons in the rkmmodelclass diagram, the button uses an ICA_MAP to map its GA_ID to one of the &amp;quot;dummy&amp;quot; pulse attributes, RKMMOD_Up and RKMMOD_Down. When the rkmmodel receives the OM_UPDATE message about RKMMOD_Up or RKMMOD_Down, it increments or decrements its internal value.&lt;br /&gt;
&lt;br /&gt;
There is one more important thing to note about rkmmodelclass. Looking at the rkmmodelclass Object diagram above, an rkmmodel&#039;s RKMMOD_CurrVal changes because it received an OM_UPDATE message from one of its gadgets. RKMMOD_CurrVal can also change if the application explicitly set RKMMOD_CurrVal using SetAttrs() or SetGadgetAttrs().&lt;br /&gt;
&lt;br /&gt;
The primary difference between the OM_SET message that SetAttrs() sends and the OM_SET message that SetGadgetAttrs() sends is that SetAttrs() passes a NULL in opSet.ops_GInfo instead of a GadgetInfo pointer. This doesn&#039;t present a problem for the rkmmodel object, because it doesn&#039;t use the GadgetInfo structure. The problem is that when the rkmmodel notifies its targets, some of which are gadgets, they can&#039;t update their visual state because they need a GadgetInfo to render themselves. For this reason, the rkmmodelclass dispatcher returns a positive non-zero value when an attribute change occurs that could cause a change in the visual state of any objects in its broadcast list. An application that uses rkmmodelclass must test the return value when calling SetAttrs() on an rkmmodelclass object to tell if the attribute change requires a visual refresh of the gadgets (see the Intuition Autodocs for RefreshGadgets()).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| &#039;&#039;BOOPSI Dispatchers Can Execute on Intuition&#039;s Context.&#039;&#039; Notice that the gadgets in the figure above send OM_UPDATE messages to the rkmmodel when the user manipulates them. Because Intuition handles the user input that triggers the OM_UPDATE messages, Intuition itself is sending the OM_UPDATE messages. This means the rkmmodelclass dispatcher must be able to run on Intuition‚Äôs context, which puts some limitations on what the dispatcher is permitted to do: it can&#039;t use dos.library, it can&#039;t wait on application signals or message ports and it can&#039;t call any Intuition functions which might wait on Intuition.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Although rkmmodelclass serves as an example of a class, it leaves a little to be desired in a real-world implementation. To create the &amp;quot;prop-integer-up/down&amp;quot; super gadget from the diagram above, the application has to create, initialize, and link nine BOOPSI objects, which is tedious, especially if the application needs several of these super gadgets. Ideally, all these functions would be rolled into some subclass of gadgetclass. If there were such a class, an application would only have to create one instance of this subclass to get such a gadget. When the subclass received an OM_NEW message, it would take care of creating, initializing, and linking all of the BOOPSI objects that make up the whole super gadget.&lt;br /&gt;
&lt;br /&gt;
=== White Boxes: The Transparent Base Classes ===&lt;br /&gt;
&lt;br /&gt;
BOOPSI gadgets and images were designed to be backwards compatible with the old Intuition Gadgets and Images, so as part of their instance data, both types of objects have the old Intuition structures built into them. When NewObject() creates a new gadget or image object, the pointer it returns points to the object&#039;s embedded Gadget or Image corresponding structure. Because Intuition can tell the difference between BOOPSI images and gadgets and the original images and gadgets, applications can use BOOPSI images and gadgets interchangeably with the older Intuition entities.&lt;br /&gt;
&lt;br /&gt;
Although normally considered a &amp;quot;programming sin&amp;quot;, in some cases it is legal for class dispatchers to directly manipulate some internal fields of certain BOOPSI objects. For compatibility reasons, a BOOPSI image or gadget object contains an actual Image or Gadget structure. These objects are instances of the &#039;&#039;Transparent Base Classes&#039;&#039;, imageclass and gadgetclass.&lt;br /&gt;
&lt;br /&gt;
To change an attribute of a BOOPSI object, you normally invoke the set method, OM_SET. The Intuition functions SetAttrs() and SetGadgetAttrs() invoke this method. A BOOPSI class is informed of any attribute change at that time, allowing it to react to this change. The reaction can include validating the changed attribute, changing other attributes to match, or informing other objects of the change. That is the inherent advantage of using function calls to change attributes.&lt;br /&gt;
&lt;br /&gt;
When using conventional images and gadgets, you generally modify the structure&#039;s fields directly. This operation is very fast. For conventional images and gadgets, there is no class that needs to know about the changes, so there is no problem. However, this is untrue of BOOPSI images and gadgets. Although directly modifying the BOOPSI object&#039;s internal structure would provide a performance increase over using the BOOPSI OM_SET mechanism, altering a BOOPSI object&#039;s internal structure directly will not give the class the opportunity to react to any structure changes. This violates the BOOPSI concept, and therefore cannot be done in general.&lt;br /&gt;
&lt;br /&gt;
In order to provide a balance between the flexibility of function-access and the performance of direct-access, the transparent base classes imageclass and gadgetclass do not depend on being informed of changes to certain fields in the internal Image and Gadget structures. This means that it is OK for the dispatchers of direct subclasses of imageclass and gadgetclass to modify specific fields of BOOPSI images or gadgets. Applications and indirect subclass dispatchers of imageclass or gadgetclass may not modify those fields, since their parent classes may depend on hearing about changes to these fields, which the SetAttrs() call (or a similar function) provides.&lt;br /&gt;
&lt;br /&gt;
For dispatchers of direct subclasses of imageclass, the following are the only fields of the Image structure that are alterable by application programs:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| LeftEdge || Width || ImageData&lt;br /&gt;
|-&lt;br /&gt;
| TopEdge || Height || PlanePick&lt;br /&gt;
|-&lt;br /&gt;
| PlaneOnOff&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For dispatchers of direct subclasses of gadgetclass, the following are the only fields of the Gadget structure that are alterable by application programs:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| LeftEdge || Flags || GadgetText&lt;br /&gt;
|-&lt;br /&gt;
| TopEdge || GadgetType || SpecialInfo&lt;br /&gt;
|-&lt;br /&gt;
| Width || GadgetRender || Activation&lt;br /&gt;
|-&lt;br /&gt;
| Height || SelectRender&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Under no circumstances may an application or an indirect subclass modify one of these fields, even if the subclass knows the superclasses do not depend on notification for this field. This is the only way to preserve the possibility for future enhancements to that superclass. Note that these fields are not alterable while the gadget or image object is in use (for example, when it is attached to a window).&lt;br /&gt;
&lt;br /&gt;
== BOOPSI Gadgets ==&lt;br /&gt;
&lt;br /&gt;
One of the major enhancements to Intuition is the implementation of customizable BOOPSI gadgets. BOOPSI gadgets are not limited by dependencies upon Intuition Image and Gadget structures. Unlike Release 1.3 gadgets, which were handled exclusively by Intuition, BOOPSI gadgets handle their own rendering and their own user input.&lt;br /&gt;
&lt;br /&gt;
Since BOOPSI gadgets draw themselves, there is almost no restriction on what they can look like. A BOOPSI gadget can use graphics.library RastPort drawing functions to draw vector-based imagery which the gadget can scale to any dimension. Instead of just a two-state Boolean gadget, a BOOPSI gadget can have any number of states, each of which has its own imagery. If a programmer wanted to he could even make a BOOPSI gadget that uses the animation system to render itself.&lt;br /&gt;
&lt;br /&gt;
Because BOOPSI gadgets handle their own input, they see all the user&#039;s input, which the gadget is free to interpret. While the user has a BOOPSI gadget selected, the gadget can track mouse moves, process mouse and keyboard key presses, or watch the timer events.&lt;br /&gt;
&lt;br /&gt;
The power of a BOOPSI gadget is not limited to its ability to handle its own rendering and user input. BOOPSI gadgets are also BOOPSI objects so the gain all the benefits BOOPSI provides. This means all BOOPSI gadgets inherit the methods and attributes from their superclasses. BOOPSI gadgets can use BOOPSI images to take care of rendering their imagery. A BOOPSI gadget could be a &amp;quot;composite&amp;quot; gadget that is composed of several BOOPSI gadgets, images, and models.&lt;br /&gt;
&lt;br /&gt;
=== The BOOPSI Gadget Methods ===&lt;br /&gt;
&lt;br /&gt;
Intuition drives a BOOPSI gadget by sending it BOOPSI messages. Intuition uses a series of five BOOPSI methods:&lt;br /&gt;
&lt;br /&gt;
; GM_RENDER&lt;br /&gt;
: This method tells the gadget to render itself.&lt;br /&gt;
&lt;br /&gt;
; GM_HITTEST&lt;br /&gt;
: This method asks a gadget whether it has been &amp;quot;hit&amp;quot; by a mouse click.&lt;br /&gt;
&lt;br /&gt;
; GM_GOACTIVE&lt;br /&gt;
: This method asks a gadget if it wants to be the active gadget.&lt;br /&gt;
&lt;br /&gt;
; GM_HANDLEINPUT&lt;br /&gt;
: This method passes a gadget an input event.&lt;br /&gt;
&lt;br /&gt;
; GM_GOINACTIVE&lt;br /&gt;
: This method tells a gadget that it is no longer active.&lt;br /&gt;
&lt;br /&gt;
The formats of each of these BOOPSI messages differ, but they all have two things in common. Like all BOOPSI messages, each starts with their respective method ID. For each of these methods, the method ID field is followed by a pointer to a GadgetInfo structure (defined in &amp;lt;intuition/cghooks.h&amp;gt;). The GadgetInfo structure contains information about the display on which the gadget needs to render itself:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct GadgetInfo {&lt;br /&gt;
    struct Screen               *gi_Screen;&lt;br /&gt;
    struct Window               *gi_Window;     /* null for screen gadgets */&lt;br /&gt;
    struct Requester            *gi_Requester;  /* null if not GTYP_REQGADGET */&lt;br /&gt;
&lt;br /&gt;
    /* rendering information: don&#039;t use these without cloning/locking.&lt;br /&gt;
     * Official way is to call ObtainGIRPort()&lt;br /&gt;
     */&lt;br /&gt;
    struct RastPort             *gi_RastPort;&lt;br /&gt;
    struct Layer                *gi_Layer;&lt;br /&gt;
&lt;br /&gt;
    /* copy of dimensions of screen/window/g00/req(/group)&lt;br /&gt;
     * that gadget resides in.  Left/Top of this box is&lt;br /&gt;
     * offset from window mouse coordinates to gadget coordinates&lt;br /&gt;
     *  screen gadgets:                 0,0 (from screen coords)&lt;br /&gt;
     *  window gadgets (no g00):        0,0&lt;br /&gt;
     *  GTYP_GZZGADGETs (borderlayer):  0,0&lt;br /&gt;
     *  GZZ innerlayer gadget:          borderleft, bordertop&lt;br /&gt;
     *  Requester gadgets:              reqleft, reqtop&lt;br /&gt;
     */&lt;br /&gt;
    struct IBox                 gi_Domain;&lt;br /&gt;
&lt;br /&gt;
    /* these are the pens for the window or screen      */&lt;br /&gt;
    struct {&lt;br /&gt;
        UBYTE   DetailPen;&lt;br /&gt;
        UBYTE   BlockPen;&lt;br /&gt;
    }                           gi_Pens;&lt;br /&gt;
&lt;br /&gt;
    /* the Detail and Block pens in gi_DrInfo-&amp;amp;gt;dri_Pens[] are&lt;br /&gt;
     * for the screen.  Use the above for window-sensitive colors.&lt;br /&gt;
     */&lt;br /&gt;
    struct DrawInfo             *gi_DrInfo;&lt;br /&gt;
&lt;br /&gt;
    /* reserved space: this structure is extensible&lt;br /&gt;
     * anyway, but using these saves some recompilation&lt;br /&gt;
     */&lt;br /&gt;
    ULONG                       gi_Reserved[6];&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All the fields in this structure are read only.&lt;br /&gt;
&lt;br /&gt;
Although this structure contains a pointer to the gadget&#039;s RastPort structure, applications should not use it for rendering. Instead, use the intuition.library function ObtainGIRPort() to obtain a copy of the GadgetInfo&#039;s RastPort. When the gadget is finished with this RastPort, it should call ReleaseGIRPort() to relinquish the RastPort.&lt;br /&gt;
&lt;br /&gt;
==== GM_RENDER ====&lt;br /&gt;
&lt;br /&gt;
Every time Intuition feels it is necessary to redraw a BOOPSI gadget, it sends a gadget a GM_RENDER message. The GM_RENDER message (defined in &amp;lt;intuition/gadgetclass.h&amp;gt;) tells a gadget to render itself:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct gpRender&lt;br /&gt;
{&lt;br /&gt;
    ULONG             MethodID;   /* GM_RENDER */&lt;br /&gt;
    struct GadgetInfo *gpr_GInfo;&lt;br /&gt;
    struct RastPort   *gpr_RPort; /* all ready for use */&lt;br /&gt;
    LONG              gpr_Redraw; /* might be a &amp;amp;quot;highlight pass&amp;amp;quot; */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Some events that cause Intuition to send a GM_RENDER are: an application passed the gadget to OpenWindow(), the user moved or resized a gadget&#039;s window, or an application explicitly asked Intuition to refresh some gadgets.&lt;br /&gt;
&lt;br /&gt;
The GM_RENDER message contains a pointer to the gadget&#039;s RastPort so the GM_RENDER method does not have to extract it from the gpr_GInfo GadgetInfo structure using ObtainGIRPort()). The gadget renders itself according to how much imagery it needs to replace. The gpr_Redraw field contains one of three values:&lt;br /&gt;
&lt;br /&gt;
Redraw the entire gadget.&lt;br /&gt;
&lt;br /&gt;
The user has manipulated the gadget, causing a change to its imagery. Update only that part of the gadget&#039;s imagery that is effected by the user manipulating the gadget (for example, the knob and scrolling field of the prop gadget).&lt;br /&gt;
&lt;br /&gt;
If this gadget supports it, toggle to or from the highlighting imagery.&lt;br /&gt;
&lt;br /&gt;
Intuition is not the only entity that calls this method. The gadget&#039;s other methods may call this method to render the gadget when it goes through state changes. For example, as a prop gadget is following the mouse from the gadget&#039;s GM_HANDLEINPUT method, the gadget could send itself GM_RENDER messages, telling itself to update its imagery according to where the mouse has moved.&lt;br /&gt;
&lt;br /&gt;
==== GM_HITTEST ====&lt;br /&gt;
&lt;br /&gt;
When Intuition gets a left mouse button click in a window, one of the things it does is check through the window&#039;s list of gadgets to see if that click was inside the bounds of a gadget&#039;s Gadget structure (using the LeftEdge, TopEdge, Width, and Height fields). If it was (and that gadget is a BOOPSI gadget), Intuition sends that gadget a GM_HITTEST message (defined in &amp;lt;intuition/gadgetclass.h&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct gpHitTest&lt;br /&gt;
{&lt;br /&gt;
    ULONG             MethodID;     /* GM_HITTEST   */&lt;br /&gt;
    struct GadgetInfo *gpht_GInfo;&lt;br /&gt;
    struct&lt;br /&gt;
    {&lt;br /&gt;
        WORD X;                     /* Is this point inside of the gadget? */&lt;br /&gt;
        WORD Y;&lt;br /&gt;
    } gpht_Mouse;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This message contains the coordinates of the mouse click. These coordinates are relative to the upper-left of the gadget (LeftEdge, TopEdge).&lt;br /&gt;
&lt;br /&gt;
Because Intuition can only tell if the user clicked inside gadget&#039;s &amp;quot;bounding box&amp;quot;, Intuition only knows that the click was close to the gadget. Intuition uses the GM_HITTEST to ask the gadget if the click was really inside the gadget. The gadget returns GMR_GADGETHIT (defined in &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;) to tell Intuition that the user hit it, otherwise it returns zero. This method allows a gadget to be any shape or pattern, rather than just rectangular.&lt;br /&gt;
&lt;br /&gt;
==== GM_GOACTIVE/GM_HANDLEINPUT ====&lt;br /&gt;
&lt;br /&gt;
If a gadget returns GMR_GADGETHIT, Intuition will send it a GM_GOACTIVE message (defined in &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
struct gpInput                          /* Used by GM_GOACTIVE and GM_HANDLEINPUT */&lt;br /&gt;
{&lt;br /&gt;
    ULONG             MethodID;&lt;br /&gt;
    struct GadgetInfo *gpi_GInfo;&lt;br /&gt;
    struct InputEvent *gpi_IEvent;      /* The input event that triggered this method&lt;br /&gt;
                                         * (for GM_GOACTIVE, this can be NULL) */&lt;br /&gt;
    LONG              *gpi_Termination; /* For GADGETUP IntuiMessage.Code */&lt;br /&gt;
    struct&lt;br /&gt;
    {&lt;br /&gt;
        WORD X;                         /* Mouse position relative to upper          */&lt;br /&gt;
        WORD Y;                         /* left corner of gadget (LeftEdge, TopEdge) */&lt;br /&gt;
    } gpi_Mouse;&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The GM_GOACTIVE message gives a gadget the opportunity to become the active gadget. The active gadget is the gadget that is currently receiving user input. Under normal conditions, only one gadget can be the active gadget (it is possible to have more than one active gadget using a groupgclass object (See the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual for more details).&lt;br /&gt;
&lt;br /&gt;
While a gadget is active, Intuition sends it GM_HANDLEINPUT messages. Each GM_HANDLEINPUT message corresponds to a single InputEvent structure. These InputEvents can be keyboard presses, timer events, mouse moves, or mouse button presses. The message&#039;s gpi_IEvent field points to this InputEvent structure. It&#039;s up to the GM_HANDLEINPUT method to interpret the meaning of these events and update the visual state of the gadget as the user manipulates the gadget. For example, the GM_HANDLEINPUT method of a prop gadget has to track mouse events to see where the user has moved the prop gadget&#039;s knob and update the gadget&#039;s imagery to reflect the new position of the knob.&lt;br /&gt;
&lt;br /&gt;
For the GM_GOACTIVE method, the gpi_IEvent field points to the struct InputEvent that triggered the GM_GOACTIVE message. Unlike the GM_HANDLEINPUT message, GM_GOACTIVE&#039;s gpi_IEvent can be NULL. If the GM_GOACTIVE message was triggered by a function like intuition.library&#039;s ActivateGadget() and not by a real InputEvent (like the user clicking the gadget), the gpi_IEvent field will be NULL.&lt;br /&gt;
&lt;br /&gt;
For gadgets that only want to become active as a direct result of a mouse click, this difference is important. For example, the prop gadget becomes active only when the user clicks on its knob. Because the only way the user can control the prop gadget is via the mouse, it does not make sense for anything but the mouse to activate the gadget. On the other hand, a string gadget doesn&#039;t care how it is activated because, as soon as it‚Äôs active, it gets user input from the keyboard rather than the mouse. Not all gadgets can become active. Some gadgets cannot become active because they have been temporarily disabled (their Gadget.Flags GFLG_DISABLED bit is set). Other gadgets will not become active because they don&#039;t need to process input. For example, a toggle gadget won&#039;t become active because it only needs to process one input event, the mouse click that toggles the gadget (which it gets from the GM_GOACTIVE message). If a toggle gadget gets a GM_GOACTIVE message and its gpi_IEvent field is not NULL, it will toggle its state and refuse to &amp;quot;go active&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The GM_GOACTIVE method has to take care of any visual state changes to a gadget that a GM_GOACTIVE message might trigger. For example, the toggle gadget in the previous paragraph has to take care of toggling its visual state from selected imagery to unselected imagery. If the gadget goes through a state change when it becomes the active gadget, (like when a string gadget positions its cursor) GM_GOACTIVE has to take care of this.&lt;br /&gt;
&lt;br /&gt;
The return values of both GM_GOACTIVE and GM_HANDLEINPUT tell Intuition whether or not the gadget wants to be active. A gadget&#039;s GM_GOACTIVE method returns GMR_MEACTIVE (defined in &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;) if it wants to become the active gadget. A gadget&#039;s GM_HANDLEINPUT method returns GMR_MEACTIVE if it wants to remain the active gadget. If a gadget either does not want to become or remain the active gadget, it returns one of the &amp;quot;go inactive&amp;quot; return values:&lt;br /&gt;
&lt;br /&gt;
; GMR_NOREUSE&lt;br /&gt;
: Tells Intuition to throw away the gpInput.gpi_IEvent InputEvent.&lt;br /&gt;
&lt;br /&gt;
; GMR_REUSE&lt;br /&gt;
: Tells Intuition to process the gpInput.gpi_IEvent InputEvent.&lt;br /&gt;
&lt;br /&gt;
; GMR_NEXTACTIVE&lt;br /&gt;
: Tells Intuition to throw away the gpInput.gpi_IEvent InputEvent and activate the next GFLG_TagCycle gadget.&lt;br /&gt;
&lt;br /&gt;
; GMR_PREVACTIVE&lt;br /&gt;
: Tells Intuition to throw away the gpInput.gpi_IEvent InputEvent and activate the previous GFLG_TagCycle gadget.&lt;br /&gt;
&lt;br /&gt;
GMR_NOREUSE tells Intuition that the gadget does not want to be active and to throw away the InputEvent that triggered the message. For example, an active prop gadget returns GMR_NOREUSE when the user lets go of the left mouse button (thus letting go of the prop gadget&#039;s knob).&lt;br /&gt;
&lt;br /&gt;
For the GM_HANDLEINPUT method, a gadget can also return GMR_REUSE, which tells Intuition to reuse the InputEvent. For example, if the user clicks outside the active string gadget, that string gadget returns GMR_REUSE. Intuition can now process that mouse click, which can be over another gadget. Another case where a string gadget returns GMR_REUSE is when the user pushes the right mouse button (the menu button). The string gadget becomes inactive and the menu button InputEvent gets reused. Intuition sees this event and tries to pop up the menu bar.&lt;br /&gt;
&lt;br /&gt;
For the GM_GOACTIVE method, a gadget must not return GMR_REUSE. If a gadget gets a GM_GOACTIVE message from Intuition and the message has an gpi_IEvent, the message was triggered by the user clicking on the gadget. In this case, Intuition knows that the user is trying to select the gadget. Intuition doesn&#039;t know if the gadget can be activated, but if it can be activated, the event that triggered the activation has just taken place. If the gadget cannot become active for any reason, it must not let Intuition reuse that InputEvent as the gadget has already taken care of the the event&#039;s purpose (clicking on the gadget). In essence, the user tried to activate the gadget and the gadget refused to become active.&lt;br /&gt;
&lt;br /&gt;
The other two possible return values are GMR_NEXTACTIVE and GMR_PREVACTIVE. These tell Intuition that a gadget does not want to be active and that the InputEvent should be discarded. Intuition then looks for the next (GMR_NEXTACTIVE) or previous (GMR_PREVACTIVE) gadget that has its GFLG_TABCYCLE flag set in its Gadget.Activation field (see the gadgetclass GA_TabCycle attribute in the &#039;&#039;BOOPSI Class Reference&#039;&#039; in the Appendix B of this manual).&lt;br /&gt;
&lt;br /&gt;
For both GM_GOACTIVE and GM_HANDLEINPUT, the gadget can bitwise-OR any of these &amp;quot;go inactive&amp;quot; return values with GMR_VERIFY. The GMR_VERIFY flag tells Intuition to send a GADGETUP IntuiMessage to the gadget&#039;s window. If the gadget uses GMR_VERIFY, it has to supply a value for the IntuiMessage.Code field. It does this by passing a value in the gpInput.gpi_Termination field. This field points to a long word, the lower 16-bits of which Intuition copies into the Code field. The upper 16-bits are for future enhancements, so clear these bits.&lt;br /&gt;
&lt;br /&gt;
==== GM_GOINACTIVE ====&lt;br /&gt;
&lt;br /&gt;
After an active gadget deactivates, Intuition sends it a GM_GOINACTIVE message (defined in &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
struct gpGoInactive&lt;br /&gt;
{&lt;br /&gt;
    ULONG             MethodID;    /* GM_GOINACTIVE */&lt;br /&gt;
    struct GadgetInfo *gpgi_GInfo;&lt;br /&gt;
&lt;br /&gt;
    /* V37 field only!  DO NOT attempt to read under V36! */&lt;br /&gt;
    ULONG             gpgi_Abort; /* gpgi_Abort=1 if gadget was aborted by Intuition   */&lt;br /&gt;
                                  /* and 0 if gadget went inactive at its own request. */&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The gpgi_Abort field contains either a 0 or 1. If 0, the gadget became inactive on its own power (because the GM_GOACTIVE or GM_HANDLEINPUT method returned something besides GMR_MEACTIVE). If gpgi_Abort is 1, Intuition aborted this active gadget. Some instances where Intuition aborts a gadget include: the user clicked in another window or screen, an application removed the active gadget with RemoveGList(), and an application called ActiveWindow() on a window other than the gadget&#039;s window.&lt;br /&gt;
&lt;br /&gt;
==== The Active Gadget ====&lt;br /&gt;
&lt;br /&gt;
While a gadget is active, Intuition sends it a GM_HANDLEINPUT message for every timer pulse, mouse move, mouse click, and key press that takes place. A timer event pulse arrives about every tenth of a second. Mouse move events can arrive at a much higher rate than the timer pulses. Without even considering the keyboard, a gadget can get a lot of GM_HANDLEINPUT messages in a short amount of time. Because the active gadget has to handle a large volume of GM_HANDLEINPUT messages, the overhead of this method should be kept to a minimum.&lt;br /&gt;
&lt;br /&gt;
Because the gadget will always receive a GM_GOACTIVE message before it is active and a GM_GOINACTIVE message after it is no longer active, the gadget can use these methods to allocate, initialize, and deallocate temporary resources it needs for the GM_HANDLEINPUT method. This can significantly reduce the overhead of GM_HANDLEINPUT because it eliminates the need to allocate, initialize, and deallocate resources for every GM_HANDLEINPUT message.&lt;br /&gt;
&lt;br /&gt;
Note that the RastPort from ObtainGIRPort() is not cachable using this method. If the GM_HANDLEINPUT method needs to use a RastPort, it has to obtain and release the RastPort for every GM_HANDLEINPUT message using ObtainGIRPort() and ReleaseGIRPort().&lt;br /&gt;
&lt;br /&gt;
==== RKMButtonclass.c ====&lt;br /&gt;
&lt;br /&gt;
The following example is a sample BOOPSI gadget, RKMButClass.c. While the user has the RKMButton selected, the gadget sends an OM_UPDATE message to its ICA_TARGET for every timer event the button sees. The gadget sends notification about its RKMBUT_Pulse attribute, which is the horizontal distance in screen pixels the mouse is from the center of the button. The gadget takes care of rendering all of its imagery (as opposed to using a BOOPSI image to do it). The gadget&#039;s imagery is scalable to any dimensions and can be set (using SetGadgetAttrs()) while the gadget is in place.&lt;br /&gt;
&lt;br /&gt;
One possible use for such a gadget is as buttons for a prop gadget. If the user has the prop gadget&#039;s RKMButton selected, while the mouse is to the left of the button&#039;s center, the knob on the prop gadget moves left. While the mouse is to the right of the button&#039;s center, the knob on the prop gadget moves right. The speed at which the knob moves is proportional to the horizontal distance from the mouse to the active RKMButton.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
;/* RKMButClass.c - Example BOOPSI gadget&lt;br /&gt;
; Execute me to compile me with Lattice 5.10b&lt;br /&gt;
LC -b1 -d0 -cfistq -v -y -j73 RKMButClass.c&lt;br /&gt;
Blink FROM LIB:c.o,RKMButClass.o TO TestBut LIBRARY LIB:LC.lib,LIB:Amiga.lib&lt;br /&gt;
quit&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
#include &amp;amp;lt;exec/types.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/intuition.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/classes.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/classusr.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/imageclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/gadgetclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/cghooks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;intuition/icclass.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/tagitem.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;utility/hooks.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/exec_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/intuition_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/graphics_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/utility_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/alib_protos.h&amp;amp;gt;&lt;br /&gt;
#include &amp;amp;lt;clib/alib_stdio_protos.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;amp;lt;graphics/gfxmacros.h&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
#ifdef LATTICE&lt;br /&gt;
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */&lt;br /&gt;
int chkabort(void) { return(0); }&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
UBYTE *vers = &amp;amp;quot;\0$VER: TestBut 37.1&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
/****************      Class specifics      ****************/&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
#define RKMBUT_Pulse   (TAG_USER + 1)&lt;br /&gt;
&lt;br /&gt;
struct ButINST&lt;br /&gt;
{&lt;br /&gt;
    LONG midX, midY; /* Coordinates of middle of gadget */&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
/* ButINST has one flag:  */&lt;br /&gt;
#define ERASE_ONLY   0x00000001 /* Tells rendering routine to */&lt;br /&gt;
                                /* only erase the gadget, not */&lt;br /&gt;
                                /* rerender a new one.  This  */&lt;br /&gt;
                                /* lets the gadget erase it-  */&lt;br /&gt;
                                /* self before it rescales.   */&lt;br /&gt;
&lt;br /&gt;
/* The functions in this module */&lt;br /&gt;
Class *initRKMButGadClass(void);&lt;br /&gt;
BOOL   freeRKMButGadClass(Class *);&lt;br /&gt;
ULONG  dispatchRKMButGad(Class *, Object *, Msg);&lt;br /&gt;
void   NotifyPulse(Class *, Object *, ULONG, LONG, struct gpInput *);&lt;br /&gt;
ULONG  RenderRKMBut(Class *, struct Gadget *, struct gpRender *);&lt;br /&gt;
void   geta4(void);&lt;br /&gt;
void   MainLoop(ULONG, ULONG);&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/* The main() function connects an RKMButClass object to a BOOPSI integer gadget, which displays */&lt;br /&gt;
/* the RKMButClass gadget&#039;s RKMBUT_Pulse value.  The code scales and move the gadget while it is */&lt;br /&gt;
/* in place.                                                                                     */&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
&lt;br /&gt;
struct TagItem pulse2int[] =&lt;br /&gt;
{&lt;br /&gt;
    {RKMBUT_Pulse, STRINGA_LongVal},&lt;br /&gt;
    {TAG_END,}&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
#define INTWIDTH  40&lt;br /&gt;
#define INTHEIGHT 20&lt;br /&gt;
&lt;br /&gt;
struct Library *IntuitionBase, *UtilityBase, *GfxBase;&lt;br /&gt;
struct Window *w;&lt;br /&gt;
Class *rkmbutcl;&lt;br /&gt;
struct Gadget *integer, *but;&lt;br /&gt;
struct IntuiMessage *msg;&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
    if (IntuitionBase = OpenLibrary(&amp;amp;quot;intuition.library&amp;amp;quot;, 37L))&lt;br /&gt;
    {&lt;br /&gt;
        if (UtilityBase = OpenLibrary(&amp;amp;quot;utility.library&amp;amp;quot;, 37L))&lt;br /&gt;
        {&lt;br /&gt;
            if (GfxBase = OpenLibrary(&amp;amp;quot;graphics.library&amp;amp;quot;, 37L))&lt;br /&gt;
            {&lt;br /&gt;
                if (w = OpenWindowTags(NULL,&lt;br /&gt;
                    WA_Flags,       WFLG_DEPTHGADGET | WFLG_DRAGBAR |&lt;br /&gt;
                                        WFLG_CLOSEGADGET | WFLG_SIZEGADGET,&lt;br /&gt;
                    WA_IDCMP,       IDCMP_CLOSEWINDOW,&lt;br /&gt;
                    WA_Width,           640,&lt;br /&gt;
                    WA_Height,          200,&lt;br /&gt;
                    TAG_END))&lt;br /&gt;
                {&lt;br /&gt;
                    WindowLimits(w, 450, 200, 640, 200);&lt;br /&gt;
&lt;br /&gt;
                    if (rkmbutcl = initRKMButGadClass())&lt;br /&gt;
                    {&lt;br /&gt;
                        if (integer = (struct Gadget *)NewObject(NULL,&lt;br /&gt;
                                       &amp;amp;quot;strgclass&amp;amp;quot;,&lt;br /&gt;
                                       GA_ID,            1L,&lt;br /&gt;
                                       GA_Top,           (w-&amp;amp;gt;BorderTop) + 5L,&lt;br /&gt;
                                       GA_Left,          (w-&amp;amp;gt;BorderLeft) + 5L,&lt;br /&gt;
                                       GA_Width,         INTWIDTH,&lt;br /&gt;
                                       GA_Height,        INTHEIGHT,&lt;br /&gt;
                                       STRINGA_LongVal,  0L,&lt;br /&gt;
                                       STRINGA_MaxChars, 5L,&lt;br /&gt;
                                       TAG_END))&lt;br /&gt;
                        {&lt;br /&gt;
                            if (but = (struct Gadget *)NewObject(rkmbutcl,&lt;br /&gt;
                                       NULL,&lt;br /&gt;
                                       GA_ID,       2L,&lt;br /&gt;
                                       GA_Top,      (w-&amp;amp;gt;BorderTop) + 5L,&lt;br /&gt;
                                       GA_Left,     integer-&amp;amp;gt;LeftEdge +&lt;br /&gt;
                                                        integer-&amp;amp;gt;Width + 5L,&lt;br /&gt;
                                       GA_Width,    40L,&lt;br /&gt;
                                       GA_Height,   INTHEIGHT,&lt;br /&gt;
                                       GA_Previous, integer,&lt;br /&gt;
                                       ICA_MAP,     pulse2int,&lt;br /&gt;
                                       ICA_TARGET,  integer,&lt;br /&gt;
                                       TAG_END))&lt;br /&gt;
                            {&lt;br /&gt;
                                AddGList(w, integer, -1, -1, NULL);&lt;br /&gt;
                                RefreshGList(integer, w, NULL, -1);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to resize gadget Height&amp;amp;quot;,&lt;br /&gt;
                                    NULL);&lt;br /&gt;
                                MainLoop(TAG_DONE, 0L);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to resize gadget Width&amp;amp;quot;,&lt;br /&gt;
                                    NULL);&lt;br /&gt;
                                MainLoop(GA_Height, 100L);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to resize gadget Y position&amp;amp;quot;,&lt;br /&gt;
                                    NULL);&lt;br /&gt;
                                MainLoop(GA_Width, 100L);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to resize gadget X position&amp;amp;quot;,&lt;br /&gt;
                                    NULL);&lt;br /&gt;
                                MainLoop(GA_Top, but-&amp;amp;gt;TopEdge + 20);&lt;br /&gt;
&lt;br /&gt;
                                SetWindowTitles(w,&lt;br /&gt;
                                    &amp;amp;quot;&amp;amp;lt;-- Click to quit&amp;amp;quot;, NULL);&lt;br /&gt;
                                MainLoop(GA_Left, but-&amp;amp;gt;LeftEdge + 20);&lt;br /&gt;
&lt;br /&gt;
                                RemoveGList(w, integer, -1);&lt;br /&gt;
                                DisposeObject(but);&lt;br /&gt;
                            }&lt;br /&gt;
                            DisposeObject(integer);&lt;br /&gt;
                        }&lt;br /&gt;
                        freeRKMButGadClass(rkmbutcl);&lt;br /&gt;
                        }&lt;br /&gt;
                    CloseWindow(w);&lt;br /&gt;
                }&lt;br /&gt;
                CloseLibrary(GfxBase);&lt;br /&gt;
                }&lt;br /&gt;
                CloseLibrary(UtilityBase);&lt;br /&gt;
        }&lt;br /&gt;
        CloseLibrary(IntuitionBase);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void MainLoop(ULONG attr, ULONG value)&lt;br /&gt;
{&lt;br /&gt;
    ULONG done = FALSE;&lt;br /&gt;
&lt;br /&gt;
    SetGadgetAttrs(but, w, NULL, attr, value, TAG_DONE);&lt;br /&gt;
&lt;br /&gt;
    while (done == FALSE)&lt;br /&gt;
    {&lt;br /&gt;
        WaitPort((struct MsgPort *)w-&amp;amp;gt;UserPort);&lt;br /&gt;
        while (msg = (struct IntuiMessage *)&lt;br /&gt;
           GetMsg((struct MsgPort *)w-&amp;amp;gt;UserPort))&lt;br /&gt;
        {&lt;br /&gt;
            if (msg-&amp;amp;gt;Class == IDCMP_CLOSEWINDOW)&lt;br /&gt;
            {&lt;br /&gt;
                done = TRUE;&lt;br /&gt;
            }&lt;br /&gt;
            ReplyMsg(msg);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
/**    Make the class and set up the dispatcher&#039;s hook    **/&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
Class   *initRKMButGadClass(void)&lt;br /&gt;
{&lt;br /&gt;
    Class *cl = NULL;&lt;br /&gt;
    extern ULONG HookEntry();     /* defined in amiga.lib */&lt;br /&gt;
&lt;br /&gt;
    if ( cl =  MakeClass( NULL,&lt;br /&gt;
                &amp;amp;quot;gadgetclass&amp;amp;quot;, NULL,&lt;br /&gt;
                sizeof ( struct ButINST ),&lt;br /&gt;
                0 ))&lt;br /&gt;
    {&lt;br /&gt;
        /* initialize the cl_Dispatcher Hook    */&lt;br /&gt;
        cl-&amp;amp;gt;cl_Dispatcher.h_Entry = HookEntry;&lt;br /&gt;
        cl-&amp;amp;gt;cl_Dispatcher.h_SubEntry = dispatchRKMButGad;&lt;br /&gt;
    }&lt;br /&gt;
    return ( cl );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
/******************     Free the class      ****************/&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
BOOL freeRKMButGadClass( Class *cl )&lt;br /&gt;
{&lt;br /&gt;
    return (FreeClass(cl));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
/**********       The RKMBut class dispatcher      *********/&lt;br /&gt;
/***********************************************************/&lt;br /&gt;
ULONG dispatchRKMButGad(Class *cl, Object *o, Msg msg)&lt;br /&gt;
{&lt;br /&gt;
    struct ButINST *inst;&lt;br /&gt;
    ULONG retval = FALSE;&lt;br /&gt;
    Object *object;&lt;br /&gt;
&lt;br /&gt;
    /* SAS/C and Manx function to make sure register A4&lt;br /&gt;
       contains a pointer to global data */&lt;br /&gt;
    geta4();&lt;br /&gt;
&lt;br /&gt;
    switch (msg-&amp;amp;gt;MethodID)&lt;br /&gt;
    {&lt;br /&gt;
        case OM_NEW:       /* First, pass up to superclass */&lt;br /&gt;
            if (object = (Object *)DoSuperMethodA(cl, o, msg))&lt;br /&gt;
            {&lt;br /&gt;
                struct Gadget *g = (struct Gadget *)object;&lt;br /&gt;
&lt;br /&gt;
                            /* Initial local instance data */&lt;br /&gt;
                inst = INST_DATA(cl, object);&lt;br /&gt;
                inst-&amp;amp;gt;midX   = g-&amp;amp;gt;LeftEdge + ( (g-&amp;amp;gt;Width) / 2);&lt;br /&gt;
                inst-&amp;amp;gt;midY   = g-&amp;amp;gt;TopEdge + ( (g-&amp;amp;gt;Height) / 2);&lt;br /&gt;
&lt;br /&gt;
                retval = (ULONG)object;&lt;br /&gt;
            }&lt;br /&gt;
            break;&lt;br /&gt;
        case GM_HITTEST:&lt;br /&gt;
                   /* Since this is a rectangular gadget this  */&lt;br /&gt;
                   /* method always returns GMR_GADGETHIT.     */&lt;br /&gt;
            retval = GMR_GADGETHIT;&lt;br /&gt;
            break;&lt;br /&gt;
        case GM_GOACTIVE:&lt;br /&gt;
            inst = INST_DATA(cl, o);&lt;br /&gt;
&lt;br /&gt;
                    /* Only become active if the GM_GOACTIVE   */&lt;br /&gt;
                    /* was triggered by direct user input.     */&lt;br /&gt;
            if (((struct gpInput *)msg)-&amp;amp;gt;gpi_IEvent)&lt;br /&gt;
            {&lt;br /&gt;
                       /* This gadget is now active, change    */&lt;br /&gt;
                       /* visual state to selected and render. */&lt;br /&gt;
                ((struct Gadget *)o)-&amp;amp;gt;Flags |= GFLG_SELECTED;&lt;br /&gt;
                                RenderRKMBut(cl, (struct Gadget *)o, (struct gpRender *)msg);&lt;br /&gt;
                retval = GMR_MEACTIVE;&lt;br /&gt;
            }&lt;br /&gt;
            else            /* The GM_GOACTIVE was not         */&lt;br /&gt;
                            /* triggered by direct user input. */&lt;br /&gt;
                retval = GMR_NOREUSE;&lt;br /&gt;
            break;&lt;br /&gt;
        case GM_RENDER:&lt;br /&gt;
            retval = RenderRKMBut(cl, (struct Gadget *)o, (struct gpRender *)msg);&lt;br /&gt;
            break;&lt;br /&gt;
        case GM_HANDLEINPUT:   /* While it is active, this gadget sends its superclass an        */&lt;br /&gt;
                               /* OM_NOTIFY pulse for every IECLASS_TIMER event that goes by     */&lt;br /&gt;
                               /* (about one every 10th of a second).  Any object that is        */&lt;br /&gt;
                               /* connected to this gadget will get A LOT of OM_UPDATE messages. */&lt;br /&gt;
            {&lt;br /&gt;
                struct Gadget *g = (struct Gadget *)o;&lt;br /&gt;
                struct gpInput *gpi = (struct gpInput *)msg;&lt;br /&gt;
                struct InputEvent *ie = gpi-&amp;amp;gt;gpi_IEvent;&lt;br /&gt;
&lt;br /&gt;
                inst = INST_DATA(cl, o);&lt;br /&gt;
&lt;br /&gt;
                retval = GMR_MEACTIVE;&lt;br /&gt;
&lt;br /&gt;
                if (ie-&amp;amp;gt;ie_Class == IECLASS_RAWMOUSE)&lt;br /&gt;
                {&lt;br /&gt;
                    switch (ie-&amp;amp;gt;ie_Code)&lt;br /&gt;
                    {&lt;br /&gt;
                        case SELECTUP: /* The user let go of the gadget so return GMR_NOREUSE    */&lt;br /&gt;
                                       /* to deactivate and to tell Intuition not to reuse       */&lt;br /&gt;
                                       /* this Input Event as we have already processed it.      */&lt;br /&gt;
&lt;br /&gt;
                                       /*If the user let go of the gadget while the mouse was    */&lt;br /&gt;
                                       /*over it, mask GMR_VERIFY into the return value so       */&lt;br /&gt;
                                       /*Intuition will send a Release Verify (GADGETUP).        */&lt;br /&gt;
                            if ( ((gpi-&amp;amp;gt;gpi_Mouse).X &amp;amp;lt; g-&amp;amp;gt;LeftEdge) ||&lt;br /&gt;
                                 ((gpi-&amp;amp;gt;gpi_Mouse).X &amp;amp;gt; g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width) ||&lt;br /&gt;
                                 ((gpi-&amp;amp;gt;gpi_Mouse).Y &amp;amp;lt; g-&amp;amp;gt;TopEdge) ||&lt;br /&gt;
                                 ((gpi-&amp;amp;gt;gpi_Mouse).Y &amp;amp;gt; g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height) )&lt;br /&gt;
                                retval = GMR_NOREUSE | GMR_VERIFY;&lt;br /&gt;
                            else&lt;br /&gt;
                                retval = GMR_NOREUSE;&lt;br /&gt;
&lt;br /&gt;
                                           /* Since the gadget is going inactive, send a final   */&lt;br /&gt;
                                           /* notification to the ICA_TARGET.                    */&lt;br /&gt;
                            NotifyPulse(cl , o, 0L, inst-&amp;amp;gt;midX, (struct gp_Input *)msg);&lt;br /&gt;
                            break;&lt;br /&gt;
                        case MENUDOWN: /* The user hit the menu button. Go inactive and let      */&lt;br /&gt;
                                       /* Intuition reuse the menu button event so Intuition can */&lt;br /&gt;
                                       /* pop up the menu bar.                                   */&lt;br /&gt;
                            retval = GMR_REUSE;&lt;br /&gt;
&lt;br /&gt;
                                           /* Since the gadget is going inactive, send a final   */&lt;br /&gt;
                                           /* notification to the ICA_TARGET.                    */&lt;br /&gt;
                            NotifyPulse(cl , o, 0L, inst-&amp;amp;gt;midX, (struct gp_Input *)msg);&lt;br /&gt;
                            break;&lt;br /&gt;
                        default:&lt;br /&gt;
                            retval = GMR_MEACTIVE;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
                else if (ie-&amp;amp;gt;ie_Class == IECLASS_TIMER)&lt;br /&gt;
                              /* If the gadget gets a timer event, it sends an interim OM_NOTIFY */&lt;br /&gt;
                    NotifyPulse(cl, o, OPUF_INTERIM, inst-&amp;amp;gt;midX, gpi); /*     to its superclass. */&lt;br /&gt;
            }&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        case GM_GOINACTIVE:           /* Intuition said to go inactive.  Clear the GFLG_SELECTED */&lt;br /&gt;
                                      /* bit and render using unselected imagery.                */&lt;br /&gt;
            ((struct Gadget *)o)-&amp;amp;gt;Flags &amp;amp;amp;= ~GFLG_SELECTED;&lt;br /&gt;
            RenderRKMBut(cl, (struct Gadget *)o, (struct gpRender *)msg);&lt;br /&gt;
            break;&lt;br /&gt;
        case OM_SET:/* Although this class doesn&#039;t have settable attributes, this gadget class   */&lt;br /&gt;
                    /* does have scaleable imagery, so it needs to find out when its size and/or */&lt;br /&gt;
                    /* position has changed so it can erase itself, THEN scale, and rerender.    */&lt;br /&gt;
            if ( FindTagItem(GA_Width,  ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList) ||&lt;br /&gt;
                 FindTagItem(GA_Height, ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList) ||&lt;br /&gt;
                 FindTagItem(GA_Top,    ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList) ||&lt;br /&gt;
                 FindTagItem(GA_Left,   ((struct opSet *)msg)-&amp;amp;gt;ops_AttrList) )&lt;br /&gt;
            {&lt;br /&gt;
                struct RastPort *rp;&lt;br /&gt;
                struct Gadget *g = (struct Gadget *)o;&lt;br /&gt;
&lt;br /&gt;
                WORD x,y,w,h;&lt;br /&gt;
&lt;br /&gt;
                x = g-&amp;amp;gt;LeftEdge;&lt;br /&gt;
                y = g-&amp;amp;gt;TopEdge;&lt;br /&gt;
                w = g-&amp;amp;gt;Width;&lt;br /&gt;
                h = g-&amp;amp;gt;Height;&lt;br /&gt;
&lt;br /&gt;
                inst = INST_DATA(cl, o);&lt;br /&gt;
&lt;br /&gt;
                retval = DoSuperMethodA(cl, o, msg);&lt;br /&gt;
&lt;br /&gt;
                                                          /* Get pointer to RastPort for gadget. */&lt;br /&gt;
                if (rp = ObtainGIRPort( ((struct opSet *)msg)-&amp;amp;gt;ops_GInfo) )&lt;br /&gt;
                {&lt;br /&gt;
                    UWORD *pens = ((struct opSet *)msg)-&amp;amp;gt;ops_GInfo-&amp;amp;gt;gi_DrInfo-&amp;amp;gt;dri_Pens;&lt;br /&gt;
&lt;br /&gt;
                    SetAPen(rp, pens[BACKGROUNDPEN]);&lt;br /&gt;
                    SetDrMd(rp, JAM1);                            /* Erase the old gadget.       */&lt;br /&gt;
                    RectFill(rp, x, y, x+w, y+h);&lt;br /&gt;
&lt;br /&gt;
                    inst-&amp;amp;gt;midX = g-&amp;amp;gt;LeftEdge + ( (g-&amp;amp;gt;Width) / 2); /* Recalculate where the       */&lt;br /&gt;
                    inst-&amp;amp;gt;midY = g-&amp;amp;gt;TopEdge + ( (g-&amp;amp;gt;Height) / 2); /* center of the gadget is.    */&lt;br /&gt;
&lt;br /&gt;
                                                                  /* Rerender the gadget.        */&lt;br /&gt;
                    DoMethod(o, GM_RENDER, ((struct opSet *)msg)-&amp;amp;gt;ops_GInfo, rp, GREDRAW_REDRAW);&lt;br /&gt;
                    ReleaseGIRPort(rp);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
                retval = DoSuperMethodA(cl, o, msg);&lt;br /&gt;
            break;&lt;br /&gt;
        default:          /* rkmmodelclass does not recognize the methodID, let the superclass&#039;s */&lt;br /&gt;
                          /* dispatcher take a look at it.                                       */&lt;br /&gt;
            retval = DoSuperMethodA(cl, o, msg);&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
    return(retval);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/************** Build an OM_NOTIFY message for RKMBUT_Pulse and send it to the superclass. *******/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
void NotifyPulse(Class *cl, Object *o, ULONG flags, LONG mid, struct gpInput *gpi)&lt;br /&gt;
{&lt;br /&gt;
    struct TagItem tt[3];&lt;br /&gt;
&lt;br /&gt;
    tt[0].ti_Tag = RKMBUT_Pulse;&lt;br /&gt;
    tt[0].ti_Data = mid - ((gpi-&amp;amp;gt;gpi_Mouse).X + ((struct Gadget *)o)-&amp;amp;gt;LeftEdge);&lt;br /&gt;
&lt;br /&gt;
    tt[1].ti_Tag = GA_ID;&lt;br /&gt;
    tt[1].ti_Data = ((struct Gadget *)o)-&amp;amp;gt;GadgetID;&lt;br /&gt;
&lt;br /&gt;
    tt[2].ti_Tag = TAG_DONE;&lt;br /&gt;
&lt;br /&gt;
    DoSuperMethod(cl, o, OM_NOTIFY, tt, gpi-&amp;amp;gt;gpi_GInfo, flags);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
/*******************************   Erase and rerender the gadget.   ******************************/&lt;br /&gt;
/*************************************************************************************************/&lt;br /&gt;
ULONG RenderRKMBut(Class *cl, struct Gadget *g, struct gpRender *msg)&lt;br /&gt;
{&lt;br /&gt;
    struct ButINST *inst = INST_DATA(cl, (Object *)g);&lt;br /&gt;
    struct RastPort *rp;&lt;br /&gt;
    ULONG retval = TRUE;&lt;br /&gt;
    UWORD *pens = msg-&amp;amp;gt;gpr_GInfo-&amp;amp;gt;gi_DrInfo-&amp;amp;gt;dri_Pens;&lt;br /&gt;
&lt;br /&gt;
    if (msg-&amp;amp;gt;MethodID == GM_RENDER)   /* If msg is truly a GM_RENDER message (not a gpInput that */&lt;br /&gt;
                                      /* looks like a gpRender), use the rastport within it...   */&lt;br /&gt;
        rp = msg-&amp;amp;gt;gpr_RPort;&lt;br /&gt;
    else                              /* ...Otherwise, get a rastport using ObtainGIRPort().     */&lt;br /&gt;
        rp = ObtainGIRPort(msg-&amp;amp;gt;gpr_GInfo);&lt;br /&gt;
&lt;br /&gt;
    if (rp)&lt;br /&gt;
    {&lt;br /&gt;
        UWORD back, shine, shadow, w, h, x, y;&lt;br /&gt;
&lt;br /&gt;
        if (g-&amp;amp;gt;Flags &amp;amp;amp; GFLG_SELECTED) /* If the gadget is selected, reverse the meanings of the  */&lt;br /&gt;
        {                             /* pens.                                                   */&lt;br /&gt;
            back   = pens[FILLPEN];&lt;br /&gt;
            shine  = pens[SHADOWPEN];&lt;br /&gt;
            shadow = pens[SHINEPEN];&lt;br /&gt;
        }&lt;br /&gt;
        else&lt;br /&gt;
        {&lt;br /&gt;
            back   = pens[BACKGROUNDPEN];&lt;br /&gt;
            shine  = pens[SHINEPEN];&lt;br /&gt;
            shadow = pens[SHADOWPEN];&lt;br /&gt;
        }&lt;br /&gt;
        SetDrMd(rp, JAM1);&lt;br /&gt;
&lt;br /&gt;
        SetAPen(rp, back);          /* Erase the old gadget.       */&lt;br /&gt;
        RectFill(rp, g-&amp;amp;gt;LeftEdge,&lt;br /&gt;
                     g-&amp;amp;gt;TopEdge,&lt;br /&gt;
                     g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width,&lt;br /&gt;
                     g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height);&lt;br /&gt;
&lt;br /&gt;
        SetAPen(rp, shadow);     /* Draw shadow edge.            */&lt;br /&gt;
        Move(rp, g-&amp;amp;gt;LeftEdge + 1, g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height);&lt;br /&gt;
        Draw(rp, g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width, g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height);&lt;br /&gt;
        Draw(rp, g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width, g-&amp;amp;gt;TopEdge + 1);&lt;br /&gt;
&lt;br /&gt;
        w = g-&amp;amp;gt;Width / 4;       /* Draw Arrows - Sorry, no frills imagery */&lt;br /&gt;
        h = g-&amp;amp;gt;Height / 2;&lt;br /&gt;
        x = g-&amp;amp;gt;LeftEdge + (w/2);&lt;br /&gt;
        y = g-&amp;amp;gt;TopEdge + (h/2);&lt;br /&gt;
&lt;br /&gt;
        Move(rp, x, inst-&amp;amp;gt;midY);&lt;br /&gt;
        Draw(rp, x + w, y);&lt;br /&gt;
        Draw(rp, x + w, y + (g-&amp;amp;gt;Height) - h);&lt;br /&gt;
        Draw(rp, x, inst-&amp;amp;gt;midY);&lt;br /&gt;
&lt;br /&gt;
        x = g-&amp;amp;gt;LeftEdge + (w/2) + g-&amp;amp;gt;Width / 2;&lt;br /&gt;
&lt;br /&gt;
        Move(rp, x + w, inst-&amp;amp;gt;midY);&lt;br /&gt;
        Draw(rp, x, y);&lt;br /&gt;
        Draw(rp, x, y  + (g-&amp;amp;gt;Height) - h);&lt;br /&gt;
        Draw(rp, x + w, inst-&amp;amp;gt;midY);&lt;br /&gt;
&lt;br /&gt;
        SetAPen(rp, shine);    /* Draw shine edge.           */&lt;br /&gt;
        Move(rp, g-&amp;amp;gt;LeftEdge, g-&amp;amp;gt;TopEdge + g-&amp;amp;gt;Height - 1);&lt;br /&gt;
        Draw(rp, g-&amp;amp;gt;LeftEdge, g-&amp;amp;gt;TopEdge);&lt;br /&gt;
        Draw(rp, g-&amp;amp;gt;LeftEdge + g-&amp;amp;gt;Width - 1, g-&amp;amp;gt;TopEdge);&lt;br /&gt;
&lt;br /&gt;
        if (msg-&amp;amp;gt;MethodID != GM_RENDER) /* If we allocated a rastport, give it back.             */&lt;br /&gt;
            ReleaseGIRPort(rp);&lt;br /&gt;
    }&lt;br /&gt;
    else retval = FALSE;&lt;br /&gt;
    return(retval);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Function Reference ==&lt;br /&gt;
&lt;br /&gt;
The following are brief descriptions of functions discussed in this section. See the SDK for details on each function call.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;header&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;th align=&amp;quot;left&amp;quot;&amp;gt;&#039;&#039;&#039;Function&#039;&#039;&#039;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;th align=&amp;quot;left&amp;quot;&amp;gt;&#039;&#039;&#039;Description&#039;&#039;&#039;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;NewObjectA()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Create a new BOOPSI object (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;NewObject()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Create a new BOOPSI object (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DisposeObject()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Dispose of a BOOPSI object.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;SetAttrs()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Set one or more of a BOOPSI object&#039;s attributes (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;SetGadgetAttrs()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Set one or more of a BOOPSI object&#039;s attributes (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;GetAttr()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Obtain an attribute from a BOOPSI object.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;MakeClass()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Create a new private or public BOOPSI class.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;FreeClass()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Free a BOOPSI class created by MakeClass().&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;AddClass()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Add a public BOOPSI class to Intuition&#039;s internal list&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;of public classes.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;RemoveClass()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Remove a public BOOPSI class that was added to Intuition&#039;s&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;internal list with AddClass().&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;ObtainGIRPort()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Set up a RastPort for use by a BOOPSI gadget dispatcher.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;ReleaseGIRPort()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Free a RastPort set up by ReleaseGIRPort().&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DoMethodA()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DoMethod()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DoSuperMethodA()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object as if the object&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;was an instance of its class‚Äôs superclass (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;DoSuperMethod()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object as if the object&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;was an instance of its class‚Äôs superclass (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;CoerceMethodA()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object as if the object&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;was an instance of the specified class (tag array form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;CoerceMethod()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI message to a BOOPSI object as if the object&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;even&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;was an instance of the specified class (varargs form).&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr class=&amp;quot;odd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;SetSuperAttrs()&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;Send a BOOPSI OM_SET message to the BOOPSI object&#039;s superclass.&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gianfranco Gignina</name></author>
	</entry>
</feed>