The thread functions of MagiC 4.5
=================================

format:	tab width 5

Andreas Kromke
Apr 1, 96

How can I recognize the existance of these functions?
=====================================================

Currently, only by regarding the date of the MagiC release. Since the
version of 010496 the following shel_write modes exist

	#define SHW_THR_CREATE	20		/* doex */
	#define SHW_THR_EXIT	21
	#define SHW_THR_KILL	22



What are threads ?
==================

Thread means that one and the same program can follow different ways during
its execution - it may execute several tasks in a quasi parallel mode.
But contrary to the usual multitasking, several threads may belong to one
process or program. For example, threads share its file handles and memory
blocks. In MagiC, a thread is implemented as an application, that means it
has an own ap_id.


Why threads ?
=============

Multitasking has been introduced in order not to block the computer during
the working of any program, that means to be able to go on working with
other programs. Multithreading follows the same aim on program's level. For
example, if a word processor is just printing a document or performing a
longer search and replacing, you will be able to continue typing your text.
Calculation programs or programs with lots of file operations may create a
thread for this time-intensive tasks and handle dialogboxes in the
meantime, that means handle mouse clicks and keyboard events. Until now,
these things were only possible by starting an additional program, so as
MGFORMAT did it with FORMAT.OVL. Expansive methods of inter-process -
communication were necessary. But the old version of MGCOPY shows how a
program normally works: only the delays between file operations can be used
for the dialog box inquiries. This leads to a "hooky" handling of the
dialog while running another action in the background. MGSEARCH still works
without threads, but this program does only short file operations, so there
will remain sufficient time for a quick window handling.


Where are threads already being used ?
======================================

Threads are available under OS/2, Sun Solaris 2.x and Windows95. Under
MagiC 4.5 the new versions of MGFORMAT and MGCOPY use threads, on the one
hand to handle the dialogbox, on the other hand to perform the format/copy
actions. Unfortunately, without background DMA-transfer (bus arbitration)
you will notice it only little.


MiNT is wonderful, MagiC emulates everything only half
======================================================

MiNT does not know threads. The threads known from the MiNT/MiNTLib sources
(translator's annotation: tfork() ) only create a new process within the
same memory. The threads of MagiC follow the same concept as of Solaris
2.x.


What belongs to whom within threads ?
=====================================

Threads run within the same process context, but they have an own ap_id,
that means they are an own task.

A thread has own:

- user stack
- supervisor stack
- ap_id
- resource files (from case to case use global-field of the parent)
- menu bar
- desktop background
- windows
- message queue
- mouse pointer
- VT52 window (option)
- etv_term vector
- semaphores

It shares with the main program:

- file handles
- base page
- memory blocks
- current directory/current drive
- process id
- domain (MiNT/TOS)
- umask
- current DTA (!!!)
- Malloc flags
- command line
- environment
- signal handler
- signal mask
- VT52 window (option)

This way, for example, a thread gets an own AP_TERM message, too.


How do I create a thread ?
==========================

A thread is created by:

shel_write(SHW_THR_CREATE, isgr, 0, THREADINFO *thi, void *par);

<isgr> = 0:			start program within the VT52 window of the calling
                    application, if one has been opened
<isgr> = 1:			do not open a VT52 window
<isgr> = 2:			open new VT52 window

typedef struct {
	LONG cdecl (*proc)(void *par);
	void *user_stack;
	ULONG stacksize;
	WORD mode;		/* always set to 0L */
	LONG res1;		/* always set to 0L */
} THREADINFO;

If <user_stack> = NULL, the system itself will create the stack. If the
thread terminates, the system will free the stack, in any case. <stacksize>
must be given in any case, to allow the system to set the stack pointer to
the end of the stack. The supervisor stack will be set by the system
itself, its size cannot be influenced.

<mode> and <res1> are reserved for future extensions. In Solaris 2.x
for example, you may stop a thread up to its final start.

The return value of shel_write() is either 0 (error occured) or the ap_id
of the new thread (>0).

The launched thread executes the function <proc>, the parameter <par> is
pushed onto the stack.
<proc> must not change the CPU registers d0-d2/a0-a2.


How do I terminate a thread ?
=============================

Normally, the thread finishes automatically with the end of the procedure
<proc>, that means by the CPU command "rts". This is the safest and best
method.

Alternatively, a thread may terminate itself by:

shel_write(SHW_THR_EXIT, 0, 0, errcode, NULL);

return value (if OK, the function does not return):

	0 error

An error may occur, if

	-	the caller is not a thread, but something else
	-	the thread has made a Pexec() in the meantime

If the thread has made a Pexec(), first the current process must be
terminated before the thread may terminate itself.

In case of emergency a thread may be terminated by the main program. In
normal cases this will not be necessary, because all corresponding threads
of a main program will be terminated automatically.
The main program terminates the thread by:

shel_write(SHW_THR_KILL, 0, ap_id, NULL, NULL);

return value:

	0 error
	1 OK

An error may happen, if

	-	the ap_id is invalid
	-	the thread has alread terminated by itself
	-	under ap_id does not run a thread but something else
 	-	the thread does not belong to the caller

Even if the return value is 1, you have to notice, that for the case
that the thread may have started another program by Pexec() in the
meantime, only this other program will be terminated by Pterm(EBREAK). The
thread will only be terminated, if the caller has received THR_EXIT.

caution: please notice, that all memory allocted by the thread belongs to
         the process, that means it will not automatically be freed with
         the end of the thread. The same is for open files which will
         be closed just at the end of the program.



How may I determine wether a thread has terminated ?
====================================================

With the end of any thread the thread or the application, wich has
created the just terminated thread, will get the message THR_EXIT (compare
CH_EXIT):

     word[0] = THR_EXIT (88)
     word[3] = AES id of the terminated thread
     word[4,5] = <errcode>	(LONG!!!)

With the end of any thread the following things will happen:

-	the screen will be locked (wind_update(BEG_UPDATE))
-	windows, mouse, keyboard, menu bar and the desktop background will be
    released
-	the screen will be unlocked
-	all locked semaphores will be unlocked
-	the VT52 window will be closed (if open)
-	the user stack will be freed by Mfree()

The thread must ensure that its file/directory handles will be closed and
its memory will be freed, because this is not automatically done.


threads and AES calls
=====================

You are _STRONGLY_ recommended to use a MT-safe library, if you are
programming in a high level language. For example, the standard libraries
of PureC are _NOT_ usefull, because they are not re-entrant.
Before all, each thread needs its own global[] array, that means if you use
an AES-library, you have to use the mt variant of each function, this
concerns:

	WORD MT_appl_init( WORD *global );
	WORD MT_rsrc_load( char *filename, WORD *global );
	WORD MT_rsrc_free( WORD *global );
	OBJECT *MT_rsrc_gaddr( WORD type, WORD index, WORD *global );
	WORD MT_rsrc_saddr( WORD type, WORD index, OBJECT *o, WORD *global );
	WORD MT_rsrc_rcfix( RSHDR *rsh, WORD *global );

MT_appl_init() clears the global array. For that reason you have to create
an own array for each thread. If the thread shall access the resource file
of the main program, you may pass the global array of the main program to
MT_rsrc_gaddr().

I (translator's annotation: Andreas Kromke) have written a library called
MT_AES for PureC which already contains all MagiC-specific calls. The
library itself has been written in C by using portab.h, so porting to other
compilers is easy.

The AES-name of any thread is not valid. It cannot be found neiter by
appl_find() nor by appl_search().


Threads and VDI-calls
=====================

Mostly, VDI-calls are not as "critical" as AES-calls, because there are
less re-entrance problems. That means, task switches do not often happen
during a VDI-call. But these will always occure, if the VDI reads vector
fonts. This is because hard disks accesses are neccessary for those fonts
and these accesses are interuptable under MagiC, that means task switches
are likely to happen. Only the Behne brothers (translator's annotation: the
authors of NVDI) are to explain which VDI-calls are interuptable. Only
these calls require re-entrant library functions. For building such a
library you may look to AES-library MT_AES.


Threads and signals
===================

If any process is stopped by the signal SIGSTOP or something similar, all
threads will be stopped. All threads will wake up again by SIGCONT.
Terminating any program by SIGTERM, SIGKILL and so on will cause all
threads to be terminated.

The signal handling is exclusively done by the main thread, that means that
one started by Pexec(). That means only the main thread will be stopped
while walking through a signal handler, with Psigreturn() jumping back to
this one.

If more than one thread manipulates the signal mask, funny things may
happen, if the old signal mask is not restored in the right order. That
means:

	thread A saves the old mask
	thread A changes the mask
	thread B saves the old mask
	thread A restores the old mask
	thread B restores the old mask

will change the signal mask in a way you do not want. A proper solution
would be to give each thread a signal mask of its own and to concatenate
all masks of all threads by OR to get an "effective" signal mask. If this
becomes necessary, I will change the kernel.


What is to know further?
========================

You are _STRONGLY_ recommended not to use a DTA, because the functions
Fsfirst/Fsnext/Fsetdta/Fgetdta are not MT-safe.

You are _STRONGLY_ recommended to synchronize any access to file handles by
suitable methods, that means two threads must never access the same file at
the same time.

Psemaphore() has just been dimensioned for threads and may be used to
synchronize both several processes and several threads of one process. At
the end of any thread all blocked semaphores will be freed automatically.

Currently, a thread should not call Pexec() but only the "main-thread".
Alternatively, a thread may launch other programs by
shel_write(SHW_PARALLEL) and wait for their exit. Theoretically, a thread
is allowed to call Pexec() as long as

    a) there is no other thread or the main thread calling Pexec()
	b) the main thread does not terminate itself

The problem is in a), that the return addresses of Pexec() are not stored
in the current process but in the parent (is likely to change in the near
future), and in b), that the parent of the process started by the thread
becomes invalid (must be handled in the near future, too).

If a thread calls Pterm(), currently only this thread will be terminated.
Or, the Pterm is just executed when the thread had made a Pexec(). Perhaps
I will change this behavior, that means I simply send a SIGTERM to the
process.


Example of programming
======================

#include <tos.h>
#include <mt_aes.h>

WORD global[15];
int	ap_id;
int  fmt_id;

LONG cdecl format_thread( struct fmt_parameter *par )
{
	WORD myglobal[15];
	int ap_id;

	/* we do not overwrite the global field of the main appl. */

     ap_id = MT_appl_init(myglobal);
     (...)
}


/*********************************************************************
* starts the format thread
*********************************************************************/

int start_format( void *param )
{
	THREADINFO thi;

	if	(fmt_id < 0)			/* thread not yet active */
		{
		thi.proc = (void *) format_thread;
		thi.user_stack = NULL;
		thi.stacksize = 4096L;
		thi.mode = 0;
		thi.res1 = 0L;
		fmt_id = shel_write(SHW_THR_CREATE, 1, 0, 
						(char *) &thi, param);
		return(fmt_id);
		}
	return(-1);				/* thread still running */
}


int void main( void )
{
	if   ((ap_id = MT_appl_init(global)) < 0)
		Pterm(-1);
	(...)
	start_format( .... );

	while(...)
		(...);

	appl_exit();
	return(0);
}