The thread functions of MagiC from Version 4.5 onwards
======================================================

Formatting: Tab width 5

Andreas Kromke
8.7.98
English translation: Peter West, April 99


How do I recognise if the functions are present?
================================================

At present only by the MagiC-date. From the version of 1.4.96 on the 
shel_write modes

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

are present.


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

Threads here mean that a program can follow several threads during 
execution, i.e. can perform several tasks in parallel so to speak. In 
contrast to the usual multitasking, however, several threads can belong 
to one process, i.e. to one program. The threads split up a process 
into the file handles and the memory blocks, for instance. In MagiC a 
thread is implemented as an application, i.e. it has its own ap_id.


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

Multitasking was introduced so that the computer wasn't completely 
blocked during the running time of a program, i.e. so that one could 
continue working with other programs. Multithreading pursues the same 
goal at the program level. While a text processor is printing or 
performing a long search and replace operation, say, one can continue 
typing in text.
Calculation programs or programs with many file operations can start a 
thread for processor-hungry tasks and still continue to service dialog 
boxes, i.e. handle mouse clicks and text input.
Up to now this was possible only by launching an extra program, like
MGFORMAT did with FORMAT.OVL. For this, complicated methods of 
communication between the programs is necessary. The old version of 
MGCOPY on the other hand shows how a program normally functions: Only 
the pauses between file operations can be used to interrogate the 
dialog boxes, so the operation while an action is running becomes 
somewhat "ropey".
MGSEARCH still works without threads, but this program only makes short 
file accesses for reading in the directories, so that sufficient time 
remains for fluid operation of the window. 


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

Threads are present under OS/2, Sun Solaris 2.x and Windows95. Under 
MagiC 4.5 the new versions of MGFORMAT and MGCOPY use threads in order to 
service the dialog box on the one hand and to perform formatting/copying 
actions on the other. However, without background-DMA one does not notice 
much of this, unfortunately.


MiNT is hot, MagiC emulates everything only half-heartedly
==========================================================

MiNT does not know threads. The functionality designated as threads in 
the MiNT source-text creates its own process, solely in the same memory 
area.
The threads in MagiC are modelled on the concept of Sun Solaris 2.x.


What belongs to whom in threads?
================================

Threads run on the same process, but have their own ap_id, i.e. they 
are their own task.

A thread has its own:

	- user stack
	- supervisor stack
	- ap_id
	- resource-file (use global-field of the parent if necessary ->MT_AES)
	- menu bar
	- desktop background
	- window
	- message queue
	- mouse pointer
	- possibly VT52 window (selectable)
	- etv_term vector
	- semaphores

It uses from the main program:

	- file handles
	- basepage
	- memory blocks
	- current directories, current drive
	- process ID
	- domain (MiNT/TOS)
	- umask
	- current DTA (!!!)
	- malloc-flags
	- command line
	- environment
	- signal handler
	- signal mask
	- possibly VT52 window (selectable)

With this a thread also gets its own AP_TERM message, for instance.


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

A thread is created with:

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

<isgr> = 0:			launch program in VT52 window of the called
					  application, if one is open
<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 0!  */
	LONG res1;		/* always set to 0L! */
} THREADINFO;

If <user_stack> = NULL, then the system creates the stack itself. If 
the thread terminates then the system releases the stack anyway.
<stacksize> has to be specified in each case so that the system can set 
the stack pointer to the end of the thread. The supervisor stack is 
set by the system itself; its size cannot be influenced.

<mode> and <res1> are reserved for possible extensions. In Solaris 2.x
one can pause a thread up to a final start with it, for instance.

Return value of shel_write() is either 0 (error has arisen) or the ap_id
of the new thread (>0).

The launched thread executes the function <proc>, and the parameter 
<par> is passed on the stack.
<proc> may alter the CPU registers d0-d2/a0-a2.


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

Normally the thread is terminated automatically at the end of the 
procedure <proc>, i.e. with the CPU command "rts". This is the safest 
and best method.

Alternatively a thread can terminate itself with:

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

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

	0 error

An error can occur if:

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

If the thread has made a Pexec() call, then the running process must be 
first terminated with Pterm() before the thread can terminate itself.


In emergencies a thread can also be terminated from within the main 
program. Normally this is not necessary, because at the termination of 
a main program all associated threads are also ended automatically.
The main program terminates the thread with:

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

Return value:

	0 error
	1 OK

An error can occur if:

	-	the ap_id is invalid
	-	the thread has already terminated itself
	-	it's not a thread running under the ap_id, but something else
	-	the thread does not belong to the caller

Even if the return value is 1, one should note that in cases where the 
thread has launched another program with Pexec() in the meantime, only 
this program will be terminated with Pterm(EBREAK). The thread will 
only be terminated when the caller has received THR_EXIT.

Warning:	One should note that the memory that the thread allocates 
		belongs to the process, i.e. it is not released automatically
		when the thread terminates. The same applies for open files,
		which are closed only on program termination.


How do I find out if a thread has terminated itself?
====================================================

At the termination of a thread, the thread or the application that had 
created the terminated one receives the message THR_EXIT (c.f. CH_EXIT):

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

At the termination of a thread the following happens:

	-	the screen is blocked (wind_update(BEG_UPDATE))
	-	windows, mouse, keyboard, menu and desktop background will be 
		released if necessary
	-	the screen will be released
	-	all blocked semaphores will be released
	-	the VT52 window will be closed if necessary
	-	the user stack will be released with Mfree()

The thread must ensure that the file/directory handles are closed and 
memory is released, if necessary, as this will not be done automatically.


Threads and AES calls
=====================

It is _IMPERATIVE_ to ensure that an MT-safe library is used if one is 
programming in a high-level language. The standard libraries of PureC 
are for the most part _NOT_ suitable, for instance, because they are 
not reentrant.
Above all each thread requires its own global[] field, i.e. when using 
an AES library the MT-variant of the function has to be used in each 
case. This affects:

	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-field, hence an own field has to be 
created for a thread. If the thread is to access the resource file of 
the main program, one can pass the global-field of the main application 
with MT_rsrc_gaddr().

For the AES calls I have written a library MT_AES for PureC, which 
already contains all MagiC-specific AES calls. The library itself is 
written in C using portab.h, permitting simple conversion for other 
compilers.

The name of a thread is invalid, i.e. it can not be found by appl_find() 
or appl_search().


Threads and DOS calls
=====================

DOS calls are not critical as the parameters are passed via the stack. 
Unfortunately there are C libraries, however, that are not reentrant.
In particular this applies generally to the function "gemdos()" (see 
below).


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

VDI calls are generally not as "critical" as AES calls, as reentrance 
problems are much rarer here. This means that when calling the VDI one 
does not switch tasks so frequently. These always occur, however, when 
the VDI accesses vector fonts; for this disk accesses are generally 
required and these are interruptible in MagiC, i.e. task switching is 
possible. Which of the VDI calls are interruptible has to be clarified 
in detail by the Behne brothers. Reentrant library functions are 
required only for these calls. For the construction of such a library 
one can take one's bearings from the AES library MT_AES.


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

If a process is paused with the signal SIGSTOP or similar then all 
threads are paused as well; with SIGCONT all threads are reawakened.
When terminating a program with SIGTERM, SIGKILL etc. all threads are 
terminated too.

The signal handling will be taken over entirely by the main thread, 
i.e. the one that was started with Pexec(). This means that during the 
processing of a signal handler only the main thread will be paused, and 
at Psigreturn() one will jump back to it.

If more than one thread is mucking about with the signal mask then 
oddities can occur if the old signal mask is not restored in the 
correct order. For instance:

	thread A rescues the old mask
	thread A alters the mask
	thread B rescues the old mask
	thread A restores the old mask
	thread B restores the old mask

alters the signal mask in an unwaned manner. A clean solution would be 
to give each thread its own signal mask and OR-combine all masks of all 
threads for the "effective" signal mask. If this becomes necessary I 
will alter the kernel appropriately.


What else is to be taken into consideration?
============================================

It's _IMPERATIVE_ to avoid DTA as the functions Fsfirst/next/setdta/getdta 
are not MT-safe.

It's _IMPERATIVE_ to synchronise access to file handles with suitable 
methods, i.e. two threads may not simultaneously access the same file.

It's _IMPERATIVE_ to avoid non-reentrant library functions such as 
"gemdos()" of the PureC library. These functions get the return-jump 
address from the stack and write it to a fixed address, which is deadly 
with several threads. This restriction unfortunately applies also to 
derived library functions: If one imitates the "Dreadlabel()" call, 
which is unfortunately missing in the library, with a "gemdos()" call, 
this is also non-reentrant and so not MT-safe.

Psemaphore() is already prepared for threads and can be used for 
synchronisation both between processes as well as between several 
threads of one process. At the termination of a thread all blocked 
semaphores will be released automatically.

At present no thread but the "main thread" should make Pexec() calls.
Alternatively, though, a thread may use shel_write(SHW_PARALLEL) to 
launch other programs and wait for their termination.
Theoretically Pexec() is permitted from within a thread, as long as:

	a) no other thread or the main thread makes a Pexec() call
	b) the main thread does not terminate itself.

The problem lies a) in the fact that the return-jump addresses with 
Pexec() are not stored in the running process but in the parent (I may 
well change this soon) and b) because the parent of the process started 
by the thread becomes invalid (I will also have to catch this soon).

If a thread makes a Pterm() call, then at present only this thread will 
be terminated, or, respectively, the Pterm will simply be executed when 
the thread has made a Pexec() call.
Perhaps I will still change this procedure, i.e. just send the process 
a SIGTERM.


Programming example
===================

#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 want to fry the global-field of the main-APPL */

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


/*********************************************************************
*
* Starts the formatting 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);
}
