What it means for a process to move from "has exited already" to "does not exist", and when this occurs, is something you must decide.
If process P is "interested" in the exit code of process Q, process P should be able to find out that exit code by calling waitpid, even if Q exits somewhat before the time P calls waitpid. As described under _exit(), precisely what is meant by "interested" is up to you.
You might implement restrictions or requirements on who may wait for which processes, like Unix does. You might also add a system call for one process to express interest in another process's exit code. If you do this, be sure to write a man page for the system call, and discuss the rationale for your choices therein in your design document.
Note that in the absence of restrictions on who may wait for what, it is possible to set up situations that may result in deadlock. Your system must (of course) in some manner protect itself from these situations, either by prohibiting them or by detecting and resolving them.
In order to make the userlevel code that ships with OS/161 work, assume that a parent process is always interested in the exit codes of its child processes generated with fork(), unless it does something special to indicate otherwise.
The options argument should be 0. You are not required to implement any options. (However, your system should check to make sure that options you do not support are not requested.)
If you desire, you may implement the Unix option WNOHANG; this causes waitpid, when called for a process that has not yet exited, to return 0 immediately instead of waiting.
The Unix option WUNTRACED, to ask for reporting of processes that stop as well as exit, is also defined in the header files, but implementing this feature is not required or necessary unless you are implementing job control.
You may also make up your own options if you find them helpful. However, please, document anything you make up.
The encoding of the exit status is comparable to Unix and is defined by the flags found in <kern/wait.h>. (Userlevel code should include <sys/wait.h> to get these definitions.) A process can exit by calling _exit() or it can exit by receiving a fatal signal. In the former case the _MKWAIT_EXIT() macro should be used with the user-supplied exit code to prepare the exit status; in the latter, the _MKWAIT_SIG() macro (or _MKWAIT_CORE() if a core file was generated) should be used with the signal number. The result encoding also allows notification of processes that have stopped; this would be used in connection with job control and with ptrace-based debugging if you were to implement those things.
To read the wait status, use the macros WIFEXITED(), WIFSIGNALED(), and/or WIFSTOPPED() to find out what happened, and then WEXITSTATUS(), WTERMSIG(), or WSTOPSIG() respectively to get the exit code or signal number. If WIFSIGNALED() is true, WCOREDUMP() can be used to check if a core file was generated. This is the same as Unix, although the value encoding is different from the historic Unix format.
If you implement WNOHANG, and WNOHANG is given, and the process specified by pid has not yet exited, waitpid returns 0.
(In Unix, but not by default OS/161, you can wait for any of several processes by passing magic values of pid, so this return value can actually be useful.)
On error, -1 is returned, and errno is set to a suitable error code for the error condition encountered.
EINVAL The options argument requested invalid or unsupported options. ECHILD The pid argument named a process that the current process was not interested in or that has not yet exited. ESRCH The pid argument named a nonexistent process. EFAULT The status argument was an invalid pointer.