Support of the signal functions in MagiC from V4.50 onwards
-----------------------------------------------------------

Andreas Kromke
7.4.96
Last change: 25.2.98 (MagiC 5)
English translation: Peter West, April 99


1. What are signals?
====================

Usually signals are regarded as messages that can only consist of one 
bit which signifies that a message has arrived. There is only a limited 
number of different messages (more exactly: 31) in total, and sending 
the same message repeatedly has no further effect.

A substantial difference to the AES message concept (->evnt_message()) 
consists of their treatment from the client's point of view. While 
messages land in a FIFO queue and are called synchronously, i.e. only 
at explicit enquiry by the user (i.e. they are "polled"), the servicing 
of a signal corresponds to an interrupt, with the difference that this 
is not triggered asynchronously, i.e. at any arbitrary time by the system, 
but by another program ("process").

The essential GEMDOS calls for signal servicing are therefore:

	Psignal() or Psigaction()

to fix the servicing of a signal, and

	Pkill()

for sending a signal to another program (process).


2. What are signals for?
========================

In practice there are two applications: In the first case one can realise 
fast inter-process communication with this method. The client can quietly 
get on with a longish calculation, but the signal will, in general, be 
serviced very quickly (unless the receiver is engaged in a critical 
file operation, in which case the execution will be delayed).

The second application is the catching of signals that are triggered 
by the system, mostly by SIGTERM (which is used to kill a program).
But one can also simply bend the etv_term vector for this, which has the 
same purpose and effect.

Essentially, therefore, the remaining purpose is the simple conversion 
of existing MiNT or UNIX programs that use signals. After all, there are 
some people who believe that a Ferrari without a carphone is not a real 
Ferrari ...


3. What signals are there?
==========================

Below is a specification of all signals defined in MiNT, and their 
behaviour under MiNT and MagiC are itemised (but be sure to also read 
MINT.DOC !!):


(0)	SIGNULL

"null".
Default: None.
This is actually a dead signal. However Pkill() can be used with SIGNULL 
to test the validity of a process-ID (e.g. of a child process), because
Pkill() returns either E_OK if the ID is valid, otherwise EFILNF. Apart 
from this the sending of SIGNULL is ignored, hence the signal can also 
not be masked or caught.

(1)	SIGHUP

"hang up".
Default: Kill process.
Should generally be sent when a terminal with which a process communicates 
is no longer valid. After receiving this signal the process should make 
no further input or output to the terminal.
In MagiC, VT52 could send this signal in the future when a terminal 
window has been closed.

(2)	SIGINT

"interrupt".
Default: Kill process.
Should generally be sent when the user inputs ^C.
Could be used by MagiC in the future in place of the existing ^C handling. 

(3)	SIGQUIT

"quit".
Default: Kill process.
Should generally be sent when the user enters ^\. Should be "harder" than
SIGINT.
Is not sent at present by MagiC/VT52.

(4)	SIGILL

"illegal instruction".
Default: Kill process.
Corresponds to the 68k exception "illegal instruction". This signal 
should not be caught.
In MiNT one may catch this signal, the exception vector is therefore 
practically bent for this process. In MagiC this does not work at present,
so it always results in eight bombs.

(5)	SIGTRAP

"trap".
Default: Kill process.
Corresponds to the 68k exception vector "trace". This signal should be 
caught only by debuggers.
In MiNT one may catch this signal, the exception vector is therefore 
practically bent for this process. In MagiC this does not work at present,
so it always results in nine bombs.

(6)	SIGABRT

"abort".
Default: Kill process.
Is generally used by the library function abort() and should normally 
not be caught.
Is not sent by the system itself.

(7)	SIGPRIV

"privilege violation."
Default: Kill process.
Corresponds to the 68k exception vector "privilege violation". This signal 
should not be caught.
In MiNT one may catch this signal, the exception vector is therefore 
practically bent for this process. In MagiC this does not work at present,
so it always results in eight bombs.

(8)	SIGFPE

"floating point exception".
Default: Ignore.
Corresponds to the 68k exception vector "division by 0". This signal can 
be ignored or caught.
In MiNT one may catch this signal, the exception vector is therefore 
practically bent for this process. In MagiC this does not work at present,
i.e. normally it can never occur.
This should change soon.

(9)	SIGKILL

"kill".
Default: Kill process. Can not be masked, caught or ignored.
When this signal is sent the process is killed forcibly, without the 
opportunity of a clear-up. Therefore SIGKILL should only be used if 
SIGTERM was unsuccessful.
Is not sent at present by MagiC itself.

(10)	SIGBUS

"bus error."
Default: Kill process.
Corresponds to the 68k exception vector "bus error". This signal should 
not be ignored or caught.
In MiNT one may catch this signal, the exception vector is therefore 
practically bent for this process. In MagiC this does not work at present,
so it always results in two bombs.
In MiNT the first occurence of this signal causes SIGBUS, SIGSEGV and 
SIGPRIV to be reset to the default routine of the system, so that a double 
bus/address/privilege error always kills the process.

(11)	SIGSEGV

"segmentation violation".
Default: Kill process.
Corresponds to the 68k exception vector "address error". This signal should 
not be ignored or caught.
In MiNT one may catch this signal, the exception vector is therefore 
practically bent for this process. In MagiC this does not work at present,
so it always results in three bombs.

(12)	SIGSYS

"bad system call."
Default: Kill process.
Is not sent at present by MagiC.

(13)	SIGPIPE

"pipe error."
Default: Kill process.
This is sent when attempting to write to a pipe that no longer exists. 
Should be masked for Drag&Drop.
Is not sent by MagiC at present, though this may change if the pipes are 
to become completely MiNT-compatible.

(14)	SIGALRM

"alarm".
Default: Kill process.
This is used by MiNT for Talarm() and serves for the handling of timeouts, 
for instance.
Talarm() does not yet exist in MagiC, so that MagiC also does not send 
this signal at present.

(15)	SIGTERM

"terminate".
Default: Kill process.
Normal signal to terminate a process cleanly. Is sent by MiNT and by MagiC 
from version 5 on during the deletion of the program file in u:\proc.

(16) Does not exist.

(17)	SIGSTOP

"stop."
Default: Suspend process. Can not be blocked, caught or ignored.
In MagiC all the threads of a process are suspended. During the "stopped"
condition no mouse clicks or keypresses etc. are lost in MagiC, as the 
corresponding messages will be evaluated at the resumption of the process 
via SIGCONT.

(18)	SIGTSTP

"terminal stop".
Default: Suspend process.
As SIGSTOP, but this signal is generally triggered by the user pressing
^Z, and can be masked/caught.
Is not sent at present by MagiC/VT52.

(19)	SIGCONT

"continue".
Default: Resume process. Can not be masked/ignored.
A suspended process (with SIGSTOP or also with Pause()) will be restarted.
Even though the signal can not be ignored, one can install a handling 
routine.
In MagiC all threads will be reawakened that are waiting after Pause() 
or were suspended via SIGSTOP or similar.

(20)	SIGCHLD

"child terminated."
Default: Ignore.
This is sent to the parent process when a child process has exited or 
been suspended. In MiNT one can specify that the signal is sent only on 
exiting (in MagiC only this mode is possible, at the suspension of a 
child process there is no signal as yet). With Pwait3() one can establish 
which child process was affected.

MagiC knows parallel Pexec() modes only from version 5.0 onwards, so older 
versions can not send this signal. The processes created with the AES call 
shel_write() are not true child processes but are fully independent, which 
means that no signal is sent when they terminate and one must wait for the 
AES message CH_EXIT.

(21)	SIGTTIN

"terminal input error".
Default: Suspend process.
A process attempted to read from a terminal that does not belong to it, 
because it was started with "pgm &" in the background, for instance.
=> MINT.DOC.
Is not sent at present by MagiC/VT52.

(22)	SIGTTOU

"terminal output error".
Default: Suspend process.
As (21), but for output (writing to a terminal).

(23) Does not exist.

(24)	SIGXCPU

"Exhaustion of CPU limit"
Default: Kill process.
The maximum CPU time allocated via Psetlimit() or by the extended 
shel_write() mode has expired.
The CPU time limitation is not implemented in MagiC at present, hence 
this signal is not sent by the system up till now.

(28)	SIGWINC

"window changed"
Default: Ignore.
The terminal size (i.e. the number of lines or columns) has changed 
(perhaps due to altering the VT52 window). A program that works with 
cursor control, for instance (say an editor) has to reconfigure itself 
appropriately.
For establishing the current window size there are Fcntl() codes in MiNT, 
which are not implemented in MagiC up till now.
It is planned that VT52 will send this signal in the future and that
MagiC should support the corresponding Fcntl() call. Until that time 
the signal is not sent in MagiC/VT52.

(29)	SIGUSR1
(30)	SIGUSR2

"User defined"
Default: Kill process.
These signals may be used by user programs for user-defined sinals. 
However, as a process is killed by default on receipt of these signals, 
one should only send the signal when the client is known.


4. Which process-specific data affect signals?
==============================================

For every process there exists under MagiC exactly one basepage, also 
called a PD (Process Descriptor). Under MagiC 4.5 this basepage is 
extended by a structure that defines the behaviour of the process on 
receipt of signals.

Each process has:

A bit vector of pending signals
-------------------------------

This bit vector describes those signals that are still waiting to be 
serviced because they are currently blocked by a signal mask, for 
instance, or because the process is in a state where it can not process 
any signals.

A signal mask
-------------

This 32-bit value defines those signals that are blocked at present as a 
bit vector. Thus when bit 30 is set, the signal SIGUSR2 will be blocked.
The signal mask is influenced not just explicitly, i.e. by system calls, 
but also implicitly by other events. During the servicing of a signal this 
is blocked, for instance, though others may occur. After servicing the 
signal this is released once more, and this can trigger further signal 
handling, for instance.
Some signals (SIGKILL, SIGSTOP, SIGCONT) can not be masked.

A table of the form "struct sigaction"
--------------------------------------

For each possible signal (1..30) a structure in the form

		typedef struct
		{
		        void    cdecl (*sa_handler)( long sig );
		        long    sa_mask;
		        int     sa_flags;
		} SIGACTION;

exists, which is specified for PureC in TOS.H. For SIGKILL however the 
table entry can not be changed.
<sa_handler> is either 0 (default signal handling by the system) or 1
(ignore signal), otherwise the address of a signal handling routine. This
is executed in user mode, and gets passed the signal number as a LONG on 
the stack (here the "cdecl" is missing in TOS.H !!!).
<sa_mask> contains the additional signals to be masked during the signal 
handling (i.e. in that case mask = oldmask+(1<<sig)+sa_mask); sa_flags 
influences the behaviour of the signal:
	SA_NOCLDSTOP (1)
		specifies in MiNT that SIGCHLD is triggered only at the 
		termination and not the suspension of a child process.
There are no other flags (as yet).

At Pexec() the signal mask and "pending" value of the child process are 
set to 0. <sa_handler> will be inherited, during which a value that is not 
1 or 0 is always set to 0. <sa_mask> and <sa_flags> are set to 0.


5. Which system calls influence the signals?
============================================

For more details see MGX_DOS.TXT. Pay attention to THREADS.TXT as well 
for the special situation of signals for programs with several threads.
Here just an overview of the GEMDOS functions:

Signal handling:
----------------

LONG Psignal(WORD sig, void *action);
LONG Psigaction( WORD sig, struct sigaction *act,	struct sigaction *oact );

Return from signal handling:
----------------------------

LONG Psigreturn( void );

Signal mask:
------------

LONG Psigblock( LONG mask );
LONG Psigsetmask( LONG mask );

Examination:
------------

LONG Psigpending( void );

Waiting for signals:
--------------------

LONG Pause( void );
LONG Psigpause( LONG mask );

Send signal:
-----------

LONG Pkill( WORD pid, WORD sig );


6. What may a signal handler do?
================================

In MiNT a signal handler may make no AES calls and no VDI calls. 
In MagiC this is, of course, no problem, because AES and VDI are not 
"foreign bodies" in the system like those suffered by character-oriented 
UNIX freaks.

To begin with, though, there is still a problem with the present versions 
of VT52: A signal handler can not yet make any keyboard inquiries in VT52, 
the keypresses will not be received. One hopes that this will change 
sometime in the future.

A signal handler runs in user mode and employs the user stack of the main 
thread, as long as this is sleeping. In MiNT the supervisor stack of the 
process is used (threads do not exist in MiNT). Hence, according to 
MINT.DOC, the nesting of signals for 4 levels or more brings a process 
down due to stack overflow. In MagiC each signal handler is its own thread 
with its own supervisor stack, so at worst only the extremely unlikely 
case of a user stack overflow can occur. If however there is insufficient 
memory available for a new thread to handle a signal, an alert box "System 
hat keinen freien Speicher mehr" ("System has no free memory left") will 
be output and one should terminate a program as quickly as possible. MagiC 
requires around 7 kB of memory for each signal handling.

One should note that the same applies as for threads (->THREADS.TXT), i.e. 
the corresponding system libraries have to be reentrant. A signal handler 
is a thread. But an additional aggravation is that the main thread is 
suspended during the servicing of the signal, so if the main thread has 
set a semaphore (wind_update()!!) a deadlock ensues if the signal handler 
also wants to set these semaphores. Furthermore, in MagiC a process can be 
suspended at almost any desired point (MagiC is reentrant, even DOS is 
suspendable!!!), so that the main thread may in some circumstances block 
important parts of the system (files, directory semaphores). Thus it is 
possible, for instance, that some files can not be deleted or opened.
Possibly I will have to restrict the occasions where a process can be 
suspended by a signal, i.e. permit the suspension only in certain 
situations.

With normal termination of a signal handler all the semaphores blocked by 
the handler will be released automatically. Furthermore the windows, 
screen background and the menu bar of the signal handler will be released 
if appropriate. One should note that a signal handler has its own AES 
message queue, i.e. evnt_message() and appl_write() should be used with 
care.

With Psigreturn() the semaphores of all signal handlers as well as that of 
the main thread will be released. However the windows/screen background/
menu bar of the signal handler will not be released. Although this would 
not have been a major problem, it should not be necessary in practice.
Psigreturn() restores the supervisor stack of the main thread, i.e. a 
setjmp/longjmp mechanism needs only to set the usp. This is insufficently 
documented in MINT.DOC and also it only works when the main thread is 
suspended via a GEMDOS call. If the AES comes into play, MultiTOS will 
crash. Psigreturn() should therefore be avoided if possible.
Psigreturn() in MagiC is not "void" as in MiNT but "LONG". If the function 
is called by something other than a signal handler, then Psigreturn() will 
return EACCDN, otherwise E_OK. If Psigreturn() has returned E_OK, then on 
no account should the handling procedure be terminated as normal by rts, 
because the return jump address on the user stack after the execution of 
Psigreturn() is invalid (this applies equally in MiNT).


7. Sample programs
==================

The first program installs a signal handler for the two signals SIGUSR1 
and SIGUSR2. With this one can test the nesting of the two signals when 
these are sent one after the other.
The program should run in VT52 or in MINIWIN.
If one presses Ctrl-Alt-Esc during the "for()" wait loop, one can see 
that MagiC creates its own thread for each signal handler. This has the 
advantage that under MagiC no events (mouse, timer, ...) can get lost 
while a signal is being serviced.

------------------------------ snip --------------------------
#include <tos.h>
#include <stdio.h>

void cdecl handler(long signr)
{
	long i;

	printf("Handler: Signal %ld received.\n", signr);
	Cconws("Wait...");
	for	(i = 0; i < 7000000L; i++)
		;
	Cconws("...OK\r\n");
}

int main( void )
{
	long ret;

	printf("My ProcID is %d.\n", Pgetpid());
	ret = (long) Psignal(SIGUSR1, handler);
	printf("Psignal => %ld\n", ret);
	ret = (long) Psignal(SIGUSR2, handler);
	printf("Psignal => %ld\n", ret);
	Cconin();
	return(0);
}
------------------------------ snap --------------------------

The second program shows the handling of the Psigreturn() call.
Under MiNT this program works only if one does not use any AES calls 
(evnt_keybd() or evnt_multi()), but uses, say, Cconin() instead.
This means that the following program works under MagiC and crashes
under MultiTOS (MiNT 1.08+AES 4.1). The problem lies probably with 
the restoration of the supervisor stack (system stack pointer) where 
MultiTOS breaks down due to the non-homogenuos concept AES<->MiNT.

------------------------------ snip --------------------------
#include <tos.h>
#include <aes.h>
#include <setjmp.h>
#include <tosdefs.h>
#include <stdio.h>

jmp_buf env;

void cdecl handler(long signr)
{
	printf("Handler: Signal %ld received.\n", signr);
	Cconws("Doing Psigreturn()\r\n");
	Psigreturn();
	longjmp(env, 1);
}

int main( void )
{
	long ssp;

	appl_init();
	printf("My ProcID is %d.\n", Pgetpid());
	Psignal(SIGUSR1, handler);

	if	(setjmp(env))
		Cconws("Coming from longjmp.\r\n");
	else	Cconws("Coming from setjmp.\r\n");
	ssp = Super(0L);
	Super((void *) ssp);
	printf("ssp = 0%08lx\n", ssp);
	evnt_keybd();
	return(0);
}
