Die Thread-Funktionen von MagiC 4.5
===================================

Formatierung:	Tabulatorbreite 5

Andreas Kromke
1.4.96


Wie erkenne ich, ob die Funktionen vorhanden sind ?
===================================================

Zur Zeit nur am MagiC-Datum. Ab der Version vom 1.4.96 sind die
shel_write-Modi 

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

vorhanden.


Was sind Threads ?
==================

Thread heit "Faden" und bedeutet, da ein Programm bei der Ausfhrung
mehrere Fden verfolgen kann, d.h. mehrere Tasks quasi parallel ausfhren
kann. Im Gegensatz zum blichen Multitasking knnen aber mehrere Threads zu
einem Proze, d.h. zu einem Programm gehren. Die Threads teilen sich dabei
z.B. die Dateihandles und die Speicherblcke. In MagiC ist ein Thread als
Applikation implementiert, d.h. hat eine eigene ap_id.


Wozu Threads ?
==============

Multitasking wurde eingefhrt, um whrend der Arbeitszeit eines Programmes
nicht den Rechner komplett zu blockieren, d.h. damit man mit anderen Programmen
weiterarbeiten kann. Multithreading verfolgt dasselbe Ziel auf Programmebene.
Whrend eine Textverarbeitung z.B. druckt oder einen lngeren Such- oder
Ersetzungsvorgang durchfhrt, kann weiter Text eingegeben werden.
Berechnungsprogramme oder Programme mit vielen Dateioperationen knnen einen
Thread fr die rechenzeitaufwendigen Aufgaben starten und dabei die
Dialogboxen bedienen, d.h. Mausklicks und Texteingaben bearbeiten.
Bisher war dies nur durch Start eines Extra-Programmes mglich, so wie es
MGFORMAT mit FORMAT.OVL gemacht hat. Dabei sind aufwendige Kommunikations-
methoden zwischen den Programmen notwendig. Die alte Version von MGCOPY zeigt
dagegen, wie ein Programm normalerweise funktioniert: Nur die Pausen zwischen
den Dateioperationen knnen zur Abfrage der Dialogboxen verwendet werden,
entsprechend "hakelig" ist die Bedienung, whrend eine Aktion luft.
MGSEARCH arbeitet noch ohne Threads, aber dieses Programm macht
nur kurze Dateioperationen zum Einlesen der Verzeichnisse, so da gengend
Zeit fr eine flssige Bedienung des Fensters brig bleibt.


Wo werden jetzt schon Threads verwendet ?
=========================================

Threads sind unter OS/2, Sun Solaris 2.x und Windows95 vorhanden.
Unter MagiC 4.5 verwenden die neuen Versionen von MGFORMAT und MGCOPY Threads,
um einerseits die Dialogbox zu bedienen und andererseits die Formatier-/
Kopieraktionen durchzufhren. Ohne Hintergrund-DMA merkt man davon leider
jedoch wenig.


MiNT ist geil, MagiC emuliert alles nur halbherzig
==================================================

MiNT kennt keine Threads. Die im MiNT-Quelltext als Threads bezeichnete
Funktionalitt erzeugt einen eigenen Proze, lediglich im selben Speicher.
Die Threads sind in MagiC dem Konzept von Sun Solaris 2.x nachempfunden.


Was gehrt wem bei Threads ?
============================

Threads laufen auf demselben Proze, haben aber eine eigene
ap_id, d.h. sind eine eigene Task.

Ein Thread hat eigene:

- Userstack
- Supervisorstack
- ap_id
- Resource-Dateien (ggf. global-Feld des parent verwenden ->MT_AES)
- Menzeile
- Desktop-Hintergrund
- Fenster
- Message Queue
- Mauszeiger
- ggf. VT52-Fenster (whlbar)
- etv_term Vektor
- Semaphoren

Er benutzt vom Hauptprogramm:

- Dateihandles
- Basepage
- Speicherblcke
- aktuelle Verzeichnisse/aktuelles Laufwerk
- Proze-ID
- Domain (MiNT/TOS)
- umask
- aktuelle DTA (!!!)
- Malloc-Flags
- Kommandozeile
- Environment
- Signal-Handler
- Signal-Maske
- ggf. VT52-Fenster (whlbar)

Damit bekommt ein Thread z.B. auch eine eigene AP_TERM-Nachricht.


Wie erstelle ich einen Thread ?
===============================

Ein Thread wird erstellt mit:

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

<isgr> = 0:			starte Programm im VT52-Fenster der aufrufenden
					 Applikation, falls eines geffnet ist.
<isgr> = 1:			ffne kein VT52 Fenster
<isgr> = 2:			ffne neues VT52 Fenster

typedef struct {
	LONG cdecl (*proc)(void *par);
	void *user_stack;
	ULONG stacksize;
	WORD mode;		/* immer auf 0 setzen! */
	LONG res1;		/* immer auf 0L setzen! */
} THREADINFO;

Ist <user_stack> = NULL, legt das System selbst den Stack an. Terminiert
der Thread, gibt das System den Stack in jedem Fall frei. <stacksize> ist in
jedem Fall anzugeben, damit das System den Stackpointer des Threads auf das
Ende des Stacks setzen kann. Der Systemstapel (supervisor stack) wird vom
System selbst festgelegt, die Gre kann nicht beeinflut werden.

<mode> und <res1> sind fr mgliche Erweiterungen reserviert. In Solaris 2.x
kann man damit z.B. einen Thread bis zum endgltigen Start anhalten.

Rckgabe von shel_write() ist entweder 0 (Fehler aufgetreten) oder die ap_id
des neuen Threads (>0).

Der gestartete Thread fhrt die Funktion <proc> aus, auf dem Stack wird
der Parameter <par> bergeben.
<proc> darf die CPU-Register d0-d2/a0-a2 verndern.


Wie beende ich einen Thread ?
=============================

Normalerweise wird der Thread mit dem Ende der Prozedur <proc>, d.h. mit
dem CPU- Befehl "rts", automatisch beendet. Dies ist die sicherste und
beste Methode.

Alternativ kann sich ein Thread selbst beenden mit:

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

Rckgabewert (wenn OK, kehrt die Funktion nicht zurck):

	0 Fehler

Ein Fehler kann auftreten, wenn

	-	der Aufrufer kein Thread ist, sondern etwas anderes
	-	der Thread inzwischen Pexec() gemacht hat

Wenn der Thread Pexec() gemacht hat, mu erst der laufende Proze per
Pterm() beendet werden, bevor sich der Thread beenden kann.


Im Notfall kann ein Thread auch vom Hauptprogramm aus beendet werden.
Normalerweise ist dies nicht notwendig, weil beim Beenden des Hauptprogramms
automatisch alle zugehrigen Threads mit beendet werden.
Das Hauptprogramm beendet den Thread mit:

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

Rckgabewert:

	0 Fehler
	1 OK

Ein Fehler kann auftreten, wenn

	-	die ap_id ungltig ist
	-	der Thread sich bereits beendet hat
	-	unter der ap_id kein Thread luft, sondern etwas anderes
	-	der Thread nicht dem Aufrufer gehrt

Auch wenn der Rckgabewert 1 ist, ist zu beachten, da fr den Fall,
da der Thread inzwischen per Pexec() ein anderes Programm gestartet
hat, nur dieses Programm per Pterm(EBREAK) beendet wird. Der Thread ist
erst dann beendet, wenn der Aufrufer THR_EXIT empfangen hat.

Achtung:	Man beachte, da Speicher, den der Thread alloziert, dem
		Proze gehrt, d.h. bei Beendigung des Threads nicht
		automatisch freigegeben wird. Gleiches gilt fr offene Dateien,
		die erst bei Programmbeendigung geschlossen werden.


Wie stelle ich fest, ob sich ein Thread beendet hat ?
=====================================================

Beim Beenden eines Threads erhlt der Thread bzw. die Applikation, der/die
den beendeten erzeugt hat, die Nachricht THR_EXIT (vgl. CH_EXIT):

     word[0] = THR_EXIT (88)
     word[3] = AES id des beendeten Thread
     word[4,5] = <errcode>	(LONG!!!)

Bei der Beendigung eines Thread passiert das folgende:

-	der Bildschirm wird gesperrt (wind_update(BEG_UPDATE))
-	Fenster, Maus, Tastatur, Men und Desktophintergrund werden
	ggf. freigegeben
-	der Bildschirm wird freigegeben
-	alle gesperrten Semaphoren werden freigegeben.
-	das VT52-Fenster wird ggf. geschlossen.
-	der Userstack wird per Mfree() freigegeben

Der Thread mu sicherstellen, da ggf. Datei-/Verzeichnishandles geschlossen
und Speicher freigegeben wird, da dies nicht automatisch durchgefhrt wird.


Threads und AES-Aufrufe
=======================

Es ist _DRINGEND_ zu beachten, eine MT-sichere Bibliothek zu verwenden,
wenn in einer Hochsprache programmiert wird. Die Standardbibliotheken
von PureC sind z.B. zum groen Teil _NICHT_ geeignet, da sie nicht
reentrant sind.
Vor allen Dingen bentigt jeder Thread sein eigenes global[] Feld, d.h.
bei Verwendung einer AES-Bibliothek mu die MT-Variante der jeweiligen
Funktion verwendet werden, das betrifft:

	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() lscht das global-Feld, daher ist fr einen Thread ein
eigenes Feld anzulegen. Soll der Thread auf die Ressourcedatei des
Hauptprogramms zugreifen, kann bei MT_rsrc_gaddr() das global-Feld des
Hauptprogramms bergeben werden.

Fr die AES-Aufrufe habe ich eine Bibliothek MT_AES fr PureC geschrieben,
die bereits alle MagiC-spezifischen AES-Aufrufe enthlt. Die Bibliothek ist
selbst in C unter Verwendung von portab.h geschrieben, so da ein
Umsetzen auf andere Compiler einfach ist.

Der Name eines Threads ist ungltig, d.h. er kann per appl_find() oder
appl_search() nicht gefunden werden.


Threads und VDI-Aufrufe
=======================

VDI-Aufrufe sind meistens nicht so "kritisch" wie AES-Aufrufe, da hier
seltener ein Reentranzproblem auftritt. D.h. beim Aufruf des VDI kommt es
nicht so hufig zu Task-Wechseln. Diese treten jedoch immer auf, wenn das
VDI auf Vektorfonts zugreift; dazu sind nmlich i.a. Plattenzugriffe notwendig,
und diese sind in MagiC unterbrechbar, d.h. es sind Task-Wechsel mglich.
Welche der VDI-Aufrufe unterbrechbar sind, ist im einzelnen durch die Behnes
zu klren. Nur fr diese Aufrufe sind reentrante Bibliotheksfunktionen
erforderlich. Fr den Aufbau einer derartigen Bibliothek kann man sich an
der AES-Bibliothek MT_AES orientieren.


Threads und Signale
===================

Wird ein Proze mit dem Signal SIGSTOP o.. angehalten, werden alle
Threads angehalten, mit SIGCONT alle Threads wieder aufgeweckt. Beim
Beenden eines Programms ber SIGTERM, SIGKILL etc. werden alle Threads
beendet.

Die Signalbehandlung wird ausschlielich vom Haupt-Thread bernommen, d.h.
demjenigen, der mit Pexec() gestartet wurde. D.h. whrend der Abarbeitung
eines Signalhandlers wird nur der Haupt-Thread angehalten, bei Psigreturn()
in diesen zurckgesprungen.

Wenn mehr als ein Thread an der Signalmaske herumfummelt, kann es zu
Sonderbarkeiten kommen, falls die alte Signalmaske nicht in der richtigen
Reihenfolge wieder zurckgesetzt wird. D.h.:

	Thread A rettet alte Maske
	Thread A ndert Maske
	Thread B rettet alte Maske
	Thread A restauriert alte Maske
	Thread B restauriert alte Maske

verndert die Signalmaske ungewollt. Eine saubere Lsung wre, jedem Thread
eine eigene Signalmaske zu geben und als "effektive" Signalmaske alle Masken
aller Threads mit OR zu verknpfen. Wenn das notwendig wird, werde ich den
Kernel entsprechend ndern.


Was ist weiterhin zu beachten ?
===============================

Es ist _DRINGEND_ zu beachten, keine DTA zu verwenden, da die Funktionen
Fsfirst/next/setdta/getdta nicht MT-sicher sind.

Es mu _DRINGEND_ beachtet werden, Zugriffe auf Dateihandles mit geeigneten
Methoden zu synchronisieren, d.h. es drfen nicht zwei Threads gleichzeitig
auf dieselbe Datei zugreifen.

Psemaphore() ist bereits fr Threads ausgelegt und kann zur Synchronisation
sowohl zwischen Prozessen als auch zwischen mehreren Threads eines Prozesses
verwendet werden. Beim Beenden eines Threads werden alle blockierten Semaphoren
automatisch freigegeben.

Zur Zeit sollte kein Thread Pexec() machen, sondern nur der "Haupt-Thread".
Alternativ kann ein Thread jedoch mit shel_write(SHW_PARALLEL) andere Programme
starten und auf auf ihre Beendigung warten.
Theoretisch ist Pexec() vom Thread aus erlaubt, solange

	a) kein anderer Thread oder der Hauptthread Pexec() machen
	b) der Hauptthread sich nicht beendet

Die Problematik liegt a) darin, da Rcksprungadressen bei Pexec() nicht im
laufenden Proze, sondern im parent abgelegt werden (werde ich demnchst wohl
ndern), und b) darin, da der parent des vom Thread gestarteten Prozesses
ungltig wird (mu ich auch demnchst noch abfangen).

Wenn ein Thread Pterm() macht, wird z.Zt. nur dieser Thread beendet. Bzw.
das Pterm wird einfach ausgefhrt, wenn der Thread ein Pexec() gemacht hatte.
Vielleicht ndere ich dieses Verhalten noch, d.h. schicke dem Proze einfach
ein SIGTERM.


Programmierbeispiel
===================

#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;

	/* wir braten das global-Feld der Haupt-APPL nicht ber */

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


/*********************************************************************
*
* Startet den Formatier-Thread.
*
*********************************************************************/

int start_format( void *param )
{
	THREADINFO thi;

	if	(fmt_id < 0)			/* Thread noch nicht aktiv */
		{
		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 luft noch */
}


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

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

	appl_exit();
	return(0);
}
