In order to execute multiple processes, the operating system (GNO/ME and GS/OS in this case) has to make decisions about which process to run and when. GNO/ME supports what is termed preemptive multitasking, which means that processes are interrupted after a certain amount of time (their time slice), at which point another process is allowed to run. The changing of machine registers to make the processor execute a different process is called a context switch, and the information the operating system needs to do this is called its context. The GNO kernel maintains a list of all active processes, and assigns time slices to each process according to their order in the list. When the kernel has run through all the processes, it starts again at the beginning of the list. This is called round-robin scheduling. Under certain circumstances, a process can actually execute longer than its allotted time slice because task switches are not allowed during a GS/OS or ToolBox call. In these cases, as soon as the system call is finished the process is interrupted.
Processes can give up the rest of their time slice voluntarily (but not necessarily explicitly) in a number of ways, terminal input being the most common. In this case, the rest of the time slice is allocated to the next process in line (to help smooth out scheduling). A process waiting on some event to happen is termed blocked. There are many ways this can happen, and each will be mentioned in its place.
An important item to remember is the process ID. This is a number which uniquely identifies a process. The ID is assigned when the process is created, and is made available for reassignment when the process terminates. A great many system calls require process IDs as input. Do not confuse this with a userID, which is a system for keeping track of memory allocation by various parts of the system, and is handled (pardon the pun) by the Memory Manager tool set. Also, do not confuse Memory Manager userID's with Unix user ID's -- numbers which are assigned to the various human users of a multiuser machine.
There are two methods for creating new processes: the system call fork(2) (or fork2(2)) and the library routine exec(3) (specifics for calling these functions and others is in Appendix A Making System Calls). fork starts up a process which begins execution at an address you specify. exec starts up a process by loading an executable file (S16 or EXE). fork is used mainly for use inside a specific application, such as running shell built-ins in the background, or setting up independent entities inside a program. Forked processes have some limitations, due to the hardware design of the Apple IIgs. The parent process (the process which called fork) must still exist when the children die, either via kill or by simply exiting. This is because the forked children share the same memory space as the parent; the memory the children execute from is tagged with the parent's userID. If the parent terminated before the children, the children's code would be deallocated and likely overwritten. A second caveat with fork is the difference between it's UNIX counterpart. UNIX fork begins executing the child at a point directly after the call to fork. This cannot be accomplished on the Apple IIgs because virtual memory is required for such an operation; thus the need to specify a fork child as a C function. Note that an appropriately written assembly language program need not necessarily have these restrictions. When a process is forked, the child process is given it's own direct page and stack space under a newly allocated userID, so that when the child terminates this memory is automatically freed.
exec(3) is used when the process you wish to start is a GS/OS load file (file type S16 and EXE). exec follows the procedure outlined in the GS/OS Reference Manual for executing a program, and sets up the new program's environment as it expects. After exec has loaded the program and set up it's environment, the new process is started and exec returns immediately.
Both fork(2) and exec(3) return the process ID of the child. The parent may use this process ID to send signals to the child, or simply wait for the child to exit with the wait(2) system call; indeed, this is the most common use. Whenever a child process terminates or is stopped (See Chapter 6 Interprocess Communication), the kernel creates a packet of information which is then made available to the process' parent. If the parent is currently inside a wait call, the call returns with the information. If the parent is off doing something else, the kernel sends the parent process a SIGCHLD signal. The default is to ignore SIGCHLD, but a common technique is to install a handler for SIGCHLD, and to make a wait call inside the handler to retrieve the relevant information.
exec(3) is actually implemented as two other system calls: fork(2), and one called execve(2). execve loads a program from an executable file, and begins executing it. The current process' memory is deallocated. The shell uses a fork/execve pair explicitly, so it can set up redirection and handle job control.