I / System Calls for Process Management
Fork is a good place to start the discussion . Fork is the only way to creat a new process in MINIX 3 . It creates an exact duplicate of the original process , including all the file descriptors , registers everything . After the fork , the original process and the copy ( the parent and child ) go their separate ways . All the variables have identical values at the time of the fork , but since the parent’s data are copied to create the child , subsequent changes in one of them do not affect the other one ( The program text . which is unchangeable , is shared between parent and child ) . The fork call returns a value , which is zero in the child and equal to the child’s process identified or PID in the parent . Using the returned PID , the two processes can see which one the parent process and which one the child process .
In most case , after a fork , the child will need to execute different code from the parent . Consider the shell . It reads a command from the terminal , forks off a child process , waits for the child to execute the command , and then reads the next command when the child terminates . To wait for the child to finish , the parent executes a waitpid system call , which just waits until child terminates ( any child if more than one exists ) . Waitpid can wait for a specific child , or for any old child by setting the first parameter to 1 . When waitip completes , the address pointed to by the second paramater , statloc , will be set to the child’s exit status ( normal or abnormal termination and exit value ). Various options are also provided , specified by the third parameter . The wiatip call replaces the previous wait call , which is now obsolete but is provived for reasons of backward compatibility .
Now consider how fork is used by the shell . When a command is typed , the shell forks off a new process . This child process must execute the user command. It does this by using the execve system call , which causes its entire core image to be replaced by the file named in its first parameter . A highly simplified shell illustrating the use of fork , waitip , and execve .
In the most general case , execve has three parameter : the name of the file to be executed , a pointed to the argument array , and a pointer to the environment array . These will be described shortly . Various library routines , including execl , execv , execle , and execve , are provided to allow the parameters to be omitted or specified in various ways . Throughout this book , we will use the name exec to represent the system call invoked by all of these .
Let us consider the case of a command such as
cp file1 file2
used to copy file1 to file2 . After the shell has forked, the child process locates and executes the file cp and passes to it the names of the source and target files.
The main program of cp (and main program of most other C programs) contains the declaration
main(argc, argv, envp)
where argc is a count of the number of items on the command line, including the program name. For the example above, argc is 3.
The second parameter, argv , is a pointer to an array. Element i of that array is a pointer to the i -th string on the command line. In our example, argv  would point to the string “cp”, argv  would point to the string “file1”, and argv  would point to the string “file2”.
The third parameter of main , envp , is a pointer to the environment, an array of strings containing assignments of the form name=value used to pass information such as the terminal type and home directory name to a program
If exec seems complicated, do not despair; it is (semantically) the most complex of all the POSIX system calls. All the other ones are much simpler. As an example of a simple one, consider exit , which processes should use when they are finished executing. It has one parameter, the exit status (0 to 255), which is returned to the parent via statloc in the waitpid system call. The loworder byte of status contains the termination status, with 0 being normal termination and the other values being various error conditions. The high-order byte contains the child’s exit status (0 to 255). For example, if a parent process executes the statement
n = waitpid(1, &statloc, options);
it will be suspended until some child process terminates. If the child exits with, say, 4 as the parameter to exit , the parent will be awakened with n set to the child’s PID and statloc set to 0x0400 (the C convention of prefixing hexadecimal constants with 0x will be used throughout this book).
Processes in MINIX 3 have their memory divided up into three segments: the text segment (i.e., the program code), the data segment (i.e., the variables), and the stack segment . Between them is a gap of unused address space. The stack grows into the gap automatically, as needed, but expansion of the data segment is done explicitly by using a system call, brk , which specifies the new address where the data segment is to end. This address may be more than the current value (data segment is growing) or less than the current value (data segment is shrinking). The parameter must, of course, be less than the stack pointer or the data and stack segments would overlap, which is forbidden.
As a convenience for programmers, a library routine sbrk is provided that also changes the size of the data segment, only its parameter is the number of bytes to add to the data segment (negative parameters make the data segment smaller). It works by keeping track of the current size of the data segment, which is the value returned by brk , computing the new size, and making a call asking for that number of bytes. The brk and sbrk calls, however, are not defined by the POSIX standard. Programmers are encouraged to use the malloc library procedure for dynamically allocating storage, and the underlying implementation of malloc was not thought to be a suitable subject for standardization since few programmers use it directly.
The next process system call is also the simplest, getpid . It just returns the caller’s PID. Remember that in fork , only the parent was given the child’s PID. If the child wants to find out its own PID, it must use getpid . The getpgrp call returns the PID of the caller’s process group. setsid creates a new session and sets the process group’s PID to the caller’s. Sessions are related to an optional feature of POSIX, job control , which is not supported by MINIX 3 and which will not concern us further.
The last process management system call, ptrace , is used by debugging programs to control the program being debugged. It allows the debugger to read and write the controlled process’ memory and manage it in other ways.
II / . System Calls for Signaling
Although most forms of interprocess communication are planned, situations exist in which unexpected communication is needed. For example, if a user accidently tells a text editor to list the entire contents of a very long file, and then realizes the error, some way is needed to interrupt the editor. In MINIX 3, the user can hit the CTRL-C key on the keyboard, which sends a signal to the editor. The editor catches the signal and stops the print-out. Signals can also be used to report certain traps detected by the hardware, such as illegal instruction or floating point overflow. Timeouts are also implemented as signals.
When a signal is sent to a process that has not announced its willingness to accept that signal, the process is simply killed without further ado. To avoid this fate, a process can use the sigaction system call to announce that it is prepared to accept some signal type, and to provide the address of the signal handling procedure and a place to store the address of the current one. After a sigaction call, if a signal of the relevant type is generated (e.g., by pressing CTRL-C), the state
of the process is pushed onto its own stack, and then the signal handler is called. It may run for as long as it wants to and perform any system calls it wants to. In practice, though, signal handlers are usually fairly short. When the signal handling procedure is done, it calls sigreturn to continue where it left off before the signal. The sigaction call replaces the older signal call, which is now provided as a library procedure, however, for backward compatibility.
Signals can be blocked in MINIX 3. A blocked signal is held pending until it is unblocked. It is not delivered, but also not lost. The sigprocmask call allows a process to define the set of blocked signals by presenting the kernel with a bitmap. It is also possible for a process to ask for the set of signals currently pending but not allowed to be delivered due to their being blocked. The sigpending call returns this set as a bitmap. Finally, the sigsuspend call allows a process to atomically set the bitmap of blocked signals and suspend itself.
Instead of providing a function to catch a signal, the program may also specify the constant SIG_IGN to have all subsequent signals of the specified type ignored, or SIG_DFL to restore the default action of the signal when it occurs. The default action is either to kill the process or ignore the signal, depending upon the signal. As an example of how SIG_IGN is used, consider what happens when the shell forks off a background process as a result of
It would be undesirable for a SIGINT signal (generated by pressing CTRL-C) to affect the background process, so after the fork but before the exec , the shell does
sigaction(SIGINT, SIG_IGN, NULL);
sigaction(SIGQUIT, SIG_IGN, NULL);
to disable the SIGINT and SIGQUIT signals. (SIGQUIT is generated by CTRL-\; it is the same as SIGINT generated by CTRL-C except that if it is not caught or ignored it makes a core dump of the process killed.) For foreground processes (no ampersand), these signals are not ignored.
Hitting CTRL-C is not the only way to send a signal. The kill system call allows a process to signal another process (provided they have the same UID unrelated processes cannot signal each other). Getting back to the example of background processes used above, suppose a background process is started up, but later it is decided that the process should be terminated. SIGINT and SIGQUIT have been disabled, so something else is needed. The solution is to use the kill program, which uses the kill system call to send a signal to any process. By sending signal 9 (SIGKILL), to a background process, that process can be killed. SIGKILL cannot be caught or ignored.
For many real-time applications, a process needs to be interrupted after a specific time interval to do something, such as to retransmit a potentially lost packet over an unreliable communication line. To handle this situation, the alarm system call has been provided. The parameter specifies an interval, in seconds, after which a SIGALRM signal is sent to the process. A process may only have one alarm outstanding at any instant. If an alarm call is made with a parameter of 10 seconds, and then 3 seconds later another alarm call is made with a parameter of 20 seconds, only one signal will be generated, 20 seconds after the second call. The first signal is canceled by the second call to alarm . If the parameter to alarm is zero, any pending alarm signal is canceled. If an alarm signal is not caught, the default action is taken and the signaled process is killed.
It sometimes occurs that a process has nothing to do until a signal arrives. For example, consider a computer-aided-instruction program that is testing reading speed and comprehension. It displays some text on the screen and then calls alarm to signal it after 30 seconds. While the student is reading the text, the program has nothing to do. It could sit in a tight loop doing nothing, but that would waste CPU time that another process or user might need. A better idea is to use pause , which tells MINIX 3 to suspend the process until the next signal.