Programming with MiNT

  • II.1. MiNT friendly programs
  • II.2. Testing for the presence of MiNT
  • II.3. File handles and devices
  • II.4. MiNT extensions to GEMDOS calls
  • II.5. Using u:\proc

II.1. MiNT friendly programs

If you want your program to work well in a multitasking environment, you should obey the following rules:

  • (1) Don't hog memory. Mshrink() your initial TPA as soon as possible
    after starting, and only Malloc() as much memory as you need.
  • (2) Avoid global changes to the system (e.g. modifying the BIOS
    keyboard maps with Keytbl()); if more than one program tries to modify the same resource in incompatible ways, confusion is sure to result.
  • (3) Use supervisor mode sparingly. In the current implementation,
    processes running in supervisor mode are not preempted, and hence will hog the CPU. You should not rely on this side-effect of supervisor mode; it may go away.
  • (4) Don't write directly to screen memory; use the documented AES,
    VDI, and BIOS calls for output.
  • (5) Don't access memory that you don't own, and don't make other
    programs access memory that they aren't allowed to. The latter point means that if you install an interrupt handler, or provide a cookie that points to data or code in your program, you must make sure that the data or code pointed to is in global memory (see Chapter V., Memory protection, for details). Otherwise, when another process tries to access the data or code (for example, if an interrupt whose vector you replaced occurs) it will receive a bus error.
  • (6) If you do things in the system like exchanging mouse
    movement/button vectors, then do catch signal 15 (SIGTERM) and other signals that can kill your process, and perform cleanup operations in a signal handling routine before terminating; this makes it easy for the user to remove your process. Otherwise your process can be killed and the vectors you've installed will be pointing at empty space.

A file (mintbind.h) is provided that gives a C interface to the new MiNT system calls. Users of other programming languages will have to write the interfaces themselves. This should be relatively straightforward, so long as your compiler provides a way to call GEMDOS directly.

II.2. Testing for the presence of MiNT

The best way to check to see if MiNT is active is to check the cookie jar. MiNT installs a cookie of 0x4d694e54 (in ASCII, MiNT), with a value consisting of the major/ minor version numbers in the high/low bytes of the low word. Thus, MiNT version 1.15 has a cookie value of 0x0000010FL. This isn't the place to explain the cookie jar, but basically it's a list of (cookie, value) pairs of longwords, terminated by cookie 0; a pointer to the jar is found at 0x5a0. MiNT always installs a cookie jar; versions of TOS prior to 1.6 don't always, in which case 0x5a0 will contain 0.

II.3. File handles and devices

File handle -1 refers to the current control terminal, not necessarily the console (though this is where it points by default). BIOS handle 2 also refers to the control terminal, so that e.g. Bconout(2, c) outputs a character to the control terminal. Thus,

Fforce(-1, Fopen('U:\\DEV\\MODEM1', 2));
r = Bconin(2);

reads a character from the RS232 port under MiNT. This is done so that programs that use the BIOS for I/O will be able to run in windows or over the modem. Similarly, the GEMDOS device CON: refers to the current control terminal, so Fopen("CON:", 2) is normally equivalent to Fdup(-1). To access the physical console, use device u:\dev\console (but do this only if you really, really need the output to go to the physical console; in almost all cases the output should be sent to the control terminal, since that's where the user will be expecting it to go).

In a similar fashion, file handle -2 and bios device 1 (GEMDOS device AUX:) may be redirected away from the RS232 port (device u:\dev\modem1), and file handle -3 and BIOS device 0 (GEMDOS device PRN:) may be directed away from the Centronics printer port (device u:\dev\prn). Since both the GEMDOS handles and BIOS device numbers are redirected, any program at all will obey the redirection unless it accesses the hardware directly (or unless it was written for MiNT and specifically uses the new device names like u:\dev\centr; this should be done only if absolutely necessary!) See also the PRN= and CON= commands for mint.cnf, which provide another way to redirect the printer and console. Actually, they're just another interface to the same method of redirection.

File handles -4 and -5 are new with MiNT, and refer to the MIDI input and output devices respectively. Redirecting these handles will affect BIOS operations on bios device 4 (the MIDI port). This is so programs that do MIDI I/O using BIOS device 4 can be connected in a special kind of pipeline.

II.4. MiNT extensions to GEMDOS calls

Fsfirst()/Fsnext()

MiNT domain processes (see the Pdomain() man page) get lower case filenames from Fsfirst() or Fsnext() on a TOS filesystem. This is because most programs end up converting them to lowercase anyway, to be more Unix-like. Please don't do this translation yourself. Let MiNT handle it, because some filesystems (e.g. the minix one) are case sensitive! If you really, truly, prefer uppercase filenames, run in the TOS domain.

Fopen()/Fread()/Fwrite()/Flock()

MiNT implements the Atari file locking protocol, as described in the document GEMDOS File and Record Locking Specification. If no _FLK cookie is installed when MINT.PRG is run, one will be created.

Pexec(100, name, cmdline, environment)

Similar to Pexec(0, ...), except the calling program does not wait for the child to finish. Returns a negative error code, or the (positive) process ID of the child.

Pexec(104, name, basepage, 0L)

Similar to Pexec(4, ...); starts executing a basepage previously set up by Pexec() mode 3, 5, or 7. The caller does not wait for the child to finish. Returns a negative error code, or the process ID of the child. Note that the child's environment and basepage are owned by both the child and the parent (indeed, the child shares all memory owned by the parent). name is a pointer to a string to be used to supply a name for the new process; if it is NULL, then the parent's name is used.

Pexec(106, name, basepage, 0L)

Similar to Pexec(104,...) except that the child's environment and basepage are not owned by the parent; nor does the child share any memory allocated to the parent. Thus, when the child terminates, its memory will be freed. A program loaded with mode 3 and then started with mode 106 behaves just like one loaded and started with mode 100. In the same way, mode 3 followed by mode 6 is just like mode 0.

Pexec(200, name, cmdline, environment)

As with Pexec(0,...) and Pexec(100,...) this runs a program. However, with this variant the caller is completely replaced with the executing program. The process retains its process ID and most other attributes, but all of its memory is freed and a new address space is set up for it containing the code from the indicated program. Whereas Pexec(0,...) is like a subroutine call, Pexec(200,...) is like a goto. It returns only if an error occurs in launching the indicated program (e.g. if not enough memory is available, or the file is not found).

II.5. Using u:\proc

You can use Fopen() on u:\proc*. to get a file handle to a process. Then you can use Fcntl() to get and set things about that process by an access to its internal PROC structure, that is created by MiNT individually for each process. An example program using that structure may begin as follows:

#include <osbind.h>
#include <mintbind.h>
#include <filesys.h>    /* for PBASEADDR and other constants   */
#include <uproc.h>      /* for struct PROCESS and other things */

long baseaddr, ctxtsize, procaddr, flags;
char buf[14];
int fd;
PROCESS proc;       /* structure from uproc.h */
CONTEXT ctxt;

To identify files appearing inside the proc folder it is enough to pass its process id as the filename extender. Here's an example of opening the PROC structure of the current process:

sprintf(buf,"u:\\proc\\*.%03d",(int)Pgetpid());
fd = Fopen(buf,2);

The following functions allow to access particular fields of the PROC structure using the file descriptor obtained as shown above. If the process terminates, then the descriptor becomes invalid.

Fcntl(fd,&baseaddr,PBASEADDR);

Stores the basepage address of the process in baseaddr variable.

Fcntl(fd,&ctxtsize,PCTXTSIZE);

Stores the size of a MiNT context in ctxtsize.

Fcntl(fd,&procaddr,PPROCADDR);

Puts the address of the MiNT process structure for the process in procaddr. To read the entire process structure (defined in proc.h), do this:

Fseek(procaddr,fd,0);
Fread(fd,sizeof(proc),&proc);

To read the user context (registers, etc.) do this:

Fseek(procaddr-ctxtsize,fd,0);
Fread(fd,sizeof(ctxt),&ctxt);

To read any part of the memory owned by the process, do this:

Fseek(addr,fd,0);
Fread(fd,size,buffer);

This Fread will only work if the region you want to read is in fact owned by the process that fd refers to.

Fcntl(fd,&flags,PGETFLAGS);

Puts the low 16 bits of a process' PRGFLAGS into flags.

Fcntl(fd,&flags,PSETFLAGS);

Sets the low 16 bits of a process' PRGFLAGS from flags. Also see Chapter V., Memory protection for detailed information on PRGFLAGS field in MiNT.

If you Fopen() process zero (MiNT itself) then you can Fseek() to any address that is managed by MiNT and read or write there. This is for debuggers and similarly dangerous tools. See also Chapter V., Debugging for other uses of u:\proc.