Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!mnetor!seismo!cmcl2!husc6!ut-sally!std-unix From: std-unix@ut-sally.UUCP (Moderator, John Quarterman) Newsgroups: mod.std.unix Subject: Re: mod.std.unix P1003 job control proposal Message-ID: <5994@ut-sally.UUCP> Date: Sat, 11-Oct-86 21:44:47 EDT Article-I.D.: ut-sally.5994 Posted: Sat Oct 11 21:44:47 1986 Date-Received: Sun, 12-Oct-86 00:00:20 EDT References: <5993@ut-sally.UUCP> Organization: IEEE P1003 Portable Operating System for Computer Environments Committee Lines: 1244 Approved: jsq@sally.utexas.edu From: hplabs!hpda!hpisoa1!davel (Dave Lennert) Date: Thu, 09 Oct 86 22:33:31 -0800 As an addendum to the P1003 job control proposal text I submitted, here's a paper describing the 4.2 job control implementation, the System V incompatibilities, and how they were resolved. This was the background investigation that went into the P1003 proposal. This is the same paper that appears in the Atlanta USENIX proceedings. [ June 1986, pp. 459-474. -mod ] It uses the -ms macro package. Dave Lennert ucbvax!hpda!davel [UUCP] Hewlett-Packard - 47UX ihnp4!hplabs!hpda!davel [UUCP] 19447 Pruneridge Ave. hpda!davel@ucb-vax.ARPA [ARPA] Cupertino, CA 95014 (408) 447-6325 [AT&T] # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by davel on Thu Oct 9 22:29:34 PDT 1986 # Contents: jobpaper.ms echo x - jobpaper.ms sed 's/^@//' > "jobpaper.ms" <<'@//E*O*F jobpaper.ms//' @.de PB @.sp 1 @.RS @.nf @.. @.de PE @.fi @.RE @.sp 1 @.. @.DA @. \" Put page numbers at bottom center rather than top center @.rm CH @.ds CF - \\n(PN - @. \" UX - UNIX macro @.de UX @.ie \\n(UX \s-1UNIX\s0\\$1 @.el \{\ \s-1UNIX\s0\\$1\(dg @.FS \(dg \s-1UNIX\s0 is a trademark of AT&T. @.FE @.nr UX 1 @.\} @.. @.TL A System V Compatible Implementation of 4.2BSD Job Control @.AU David C. Lennert @.AI Hewlett-Packard Company Information Technology Group hplabs\^!\^hpda\^!\^davel @.AB This paper gives an overview of how process groups and controlling terminals are handled in System V and 4.2BSD and then discusses the effect 4.2BSD job control has on these things. A modified 4.2BSD interface is discussed which supports 4.2BSD job control functionality but in a way which allows AT&T System V compatibility. This interface has been implemented in Hewlett-Packard's @.UX system, HP-UX. @.AE @.sp 1 @.NH INTRODUCTION @.PP The job control functionality first introduced into @.UX by Jim Kulp of IIASA and later provided by 4.2BSD @.UX has become a \fIde facto\fP industry standard. However, this job control facility, as implemented in 4.2BSD, is incompatible in several respects with System V. @.PP Recently a proposal was submitted to the IEEE P1003 Portable Operating System standard committee by Sun Microsystems [Harris86] which attempts to define 4.2BSD job control functionality in a way compatible with System V. Hewlett-Packard Company has been independently developing a similar proposal. HP's proposal is almost identical to Sun's but goes beyond it to address many "corner case" areas which strongly affect System V compatibility. @.PP This paper gives an overview of the relevant areas of System V functionality which are affected. It then overviews how job control is implemented in 4.2BSD and how this impacts the System V interface. Finally, the HP-UX interface is presented and a similar overview of its implementation is given. @.PP The various overviews cover how job control signals are generated, passed, and acknowledged by the tty driver and user processes. They also explain how process groups are established and changed. @.sp 1 @.NH FUNDAMENTALS @.PP In the following discussion the reader is assumed to have an understanding of several fundamental concepts found in the @.UX operating system. For convenience these concepts are briefly reviewed here. @.NH 2 Process Groups and Controlling Terminals @.PP Every process has a unique numeric value associated with it called its \fIprocess ID\fP. Every process also has a non-unique numeric value associated with it called its \fIprocess group ID\fP. A \fIprocess group\fP is a collection of processes having identical numeric process group ID's. Typically, one process in the process group will be the \fIprocess group leader\fP. The process group leader has a process ID which is numerically equal to the process group ID associated with all processes in the process group. Typically, the process group leader is the ancestor of all other processes in the process group. @.PP A process can have a \fIcontrolling terminal\fP which is usually the login terminal of the user who created the process. A process can obtain access to its controlling terminal by opening the file \fI/dev/tty\fP. All processes in the same process group typically share the same controlling terminal. A terminal usually has a process group ID associated with it, called the \fItty group ID\fP. When a user generates a keyboard signal (e.g., by typing the interrupt character), the tty driver sends the appropriate signal to all processes which are members of the process group indicated by the tty group ID. In summary, usually, but not necessarily, all processes in the same process group share the same controlling terminal, and the tty group ID for that terminal is equal to the process group ID of the process group. @.PP For further explanation see [Roch85] and intro(2) in your favorite @.UX Programmer's Manual. @.NH 2 4.2BSD Job Control @.PP 4.2BSD job control allows users to selectively stop (suspend) the execution of processes and continue (resume) their execution at any later point. This only easily works for processes which are stopped and continued during the same login session. @.PP The user almost always employs this facility via the interactive interface jointly supplied by the system tty driver and a job control shell such as csh(1) or ksh(1). The tty driver recognizes a user-defined \fIsuspend character\fP which causes all current foreground processes to stop and the user's job control shell to resume. The job control shell provides commands which continue stopped processes in either the foreground or background. The tty driver will also stop a background process when it attempts to read from or write to the users terminal. This allows the user to finish or suspend their foreground task without interruption and continue the stopped background process at a more convenient time. @.PP To enable the system to support this, 4.2BSD job control introduces five new signals: SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, and SIGCONT. The first four signals cause a process to stop unless the signals are being caught or ignored. SIGCONT always causes a stopped process to continue. (SIGCONT has no effect on processes which are not stopped.) SIGSTOP cannot be caught or ignored. @.PP The tty driver sends some of these signals to all processes in the tty process group under the following conditions: The driver sends SIGTSTP when the user types the suspend or delayed suspend character. The driver sends SIGTTIN (SIGTTOU) when a background process attempts to read from (write to) its controlling terminal. SIGCONT is usually only sent by a job control shell when the user requests that a stopped process be continued. Of course, any signal can be sent by a user via the kill(1) command or by a program via the kill(2) system call. @.PP It should be noted that these signals can be added to a @.UX implementation in a manner which preserves source and object code compatibility. A process is not required to be aware of them. By default the signals do "the right thing." @.PP For further information see [Joy80] and [UCB83]. @.sp 1 @.NH AT&T SYSTEM V @.NH 2 Introduction @.PP System V process groups closely resemble the concept of a login session. That is, all processes spawned during the same login session tend to belong to the same process group, and keyboard signals are typically sent to all processes spawned from the login session. @.NH 2 System V Process Group Handling @.PP In System V, the only way to alter the process group associated with a process (p_pgrp) is via setpgrp(2). And this can only set the process group to equal the process ID (pid) of the process. When this happens the resulting process with pid = p_pgrp is called a process group leader. Since a process's pid can never change, once a process issues a setpgrp(2) call it irrevocably becomes a process group leader. @.PP The init(1M) process spawns all other processes on the system either directly or indirectly. Before directly spawning a process (after the fork(2) but before the exec(2)), init calls setpgrp(2). Thus all original children (not orphans) of init are forced to (irrevocably) be process group leaders. @.PP When a new process is created, it is assigned a new pid but it inherits the process group number of its parent. Thus child processes are, by default, not process group leaders (although they can become a process group leader via setpgrp(2)). @.PP When a process group leader which has a controlling terminal (see below) terminates, SIGHUP is sent to all processes in the same process group. @.PP Further, when a process group leader terminates, all processes that belong to this process group are altered to belong to no process group (their p_pgrp is set to zero). More precisely, when any process exits, all processes whose process group (p_pgrp) equals the pid of the terminating process will have their p_pgrp set to zero; this check succeeds only in the case of a terminating process group leader. @.NH 2 System V Controlling Terminals @.PP A terminal that is currently open by a process may also be a "controlling terminal" for a process group. When certain control characters are typed on a controlling terminal, signals are sent by the terminal driver to all processes that belong to the process group associated with the terminal. @.PP When a process becomes a process group leader (via setpgrp(2)) it automatically loses its controlling terminal. After this, the first terminal (that is not already a controlling terminal) opened by the process is assigned to be the controlling terminal for that process. Also, the process group associated with that terminal (t_pgrp, also known as the tty group ID) is set equal to the process group associated with the process group leader (p_pgrp). All child processes inherit the controlling terminal and process group of their parent. @.PP More precisely, in System V, the process group associated with a terminal (t_pgrp), can be changed in the following ways: @.IP (1) When a terminal is opened by a process group leader (pid == p_pgrp) that does not already have a controlling terminal, it becomes the controlling terminal for that process group (t_pgrp is set equal to p_pgrp) if it is not already a controlling terminal. @.IP (2) When a process group leader (pid == p_pgrp) dies, if it has a controlling terminal that is associated with the same process group (t_pgrp == p_pgrp), then that terminal is disassociated from that process group (t_pgrp is set to zero). @.IP (3) When the last process to have a terminal open closes that terminal, the terminal is disassociated from its process group (t_pgrp is set to zero). @.NH 2 System V Typical Scenario @.PP This is a typical scenario for the birth and death of a process group and its controlling terminal. @.PP The init(1M) process wants to enable a terminal for login. It calls fork(2) to create a new process and then calls setpgrp(2) to make the process a process group leader which also removes the process's controlling terminal. It then runs the getty(1M) program as the process via exec(2). Getty opens the terminal causing it to become getty's controlling terminal and be associated with getty's process group (t_pgrp is set to p_pgrp). Getty replaces itself with login(1) which replaces itself with a login shell, e.g., sh(1). Usually no program calls setpgrp(2) and thus all descendent processes of the login shell are in the same process group and have the same controlling terminal; keyboard signals are sent to all processes launched during this session. @.PP When a logout occurs, the login shell (which is the process group leader) dies and the controlling terminal is freed up (t_pgrp is set to zero) so that it can be claimed as a controlling terminal by a subsequent getty respawned by init. SIGHUP is sent to all processes in the same process group. The process group (p_pgrp) of all descendent processes is then set to zero. @.PP Note that there may continue to be background processes (previously started by the now defunct login shell) which continue to execute but keyboard signals will no longer be sent to these processes (since both t_pgrp and p_pgrp equal zero). @.sp 1 @.NH 4.2BSD @.NH 2 Introduction @.PP 4.2BSD process groups closely resemble the concept of a task within a login session, where a task represents a set of processes which are affected as a group by job control operations. Every time a job control shell (e.g., csh) spawns either a foreground or background command, all processes in the pipeline (and their descendents) are placed in their own unique process group with the first command in the pipeline being the process group leader. @.PP A task is in the foreground when the process group associated with the controlling terminal for the task (t_pgrp) is equal to the process group associated with the processes in the task (p_pgrp). Otherwise the task is in the background. A job control shell moves a job between the foreground and background by adjusting the terminal process group (t_pgrp) of the controlling terminal. @.PP Note that 4.2BSD forms new process groups with process group leaders much more often than System V usually does (every command versus every login). @.NH 2 4.2BSD Process Group Handling @.PP In 4.2BSD, the process group associated with a process (p_pgrp) can be altered in two ways. The first is via setpgrp(2). 4.2BSD's setpgrp(2) is analogous to System V's setpgrp(2) except that the former can affect processes other than the current process and can cause the affected process to adopt a process group other than that process's process ID (pid). Thus, unlike System V, a process can cease to be a process group leader. @.PP In addition to setpgrp(2), a process that is not a member of any process group (p_pgrp == 0) will "inherit" or join the process group associated with its controlling terminal at the time the process is assigned a controlling terminal during open(2). If the terminal being opened is not presently the controlling terminal for any process group, then the process opening the terminal will first be made a process group leader (p_pgrp will be set to p_pid) and then the terminal will become the controlling terminal for this new process group. All this is done by the tty open code. @.PP When a new process is created it inherits the process group of its parent. @.PP Unlike System V init(1M), 4.2BSD init(8) does not call setpgrp(2) when spawning other processes. All processes spawned by init inherit init's process group which happens to be zero ("not a member of any process group"). This is actually crucial for assigning controlling terminals; see below. @.NH 2 4.2BSD Controlling Terminals @.PP Unlike System V, a 4.2BSD process does not lose its controlling terminal when altering its process group (via setpgrp(2)). Also unlike System V, a 4.2BSD process that is a process group leader (pid == p_pgrp) but which has no controlling terminal does not receive a controlling terminal when opening a new terminal. @.PP A process can obtain a controlling terminal under 4.2BSD in only the following ways: @.IP (1) A process can inherit a controlling terminal from its parent. @.IP (2) A process that is not a member of any process group (p_pgrp == 0) can open any terminal and that terminal will become its controlling terminal (whether or not it is already the controlling terminal for another process). However, this can happen in one of two ways: @.IP If the terminal is not already a controlling terminal (t_pgrp == 0) then the opening process becomes a process group leader (its p_pgrp is set equal to its pid) and the terminal becomes its controlling terminal (t_pgrp is set to the new p_pgrp value). @.IP If the terminal is already a controlling terminal for another process (t_pgrp is not zero) then the opening process joins the process group already associated with the controlling terminal. That is, p_pgrp is set equal to the current t_pgrp. Note that the opening process does not become a process group leader, i.e., p_pgrp is not equal to its pid. @.IP Note that this procedure only happens during the first terminal open for a process that was either originally spawned by init or whose ancestor processes (all the way back to init) never altered their process group (p_pgrp) either by opening a terminal or calling setpgrp(2). @.PP A terminal ceases to be a controlling terminal (t_pgrp is set to zero) under 4.2BSD in the following way: @.IP (1) When the last process to have a terminal open closes that terminal then the terminal is disassociated from its process group (t_pgrp is set to zero). @.PP There are two other facilities unique to 4.2BSD which affect access to control terminals: the TIOCSPGRP ioctl(2) and vhangup(2). @.PP The TIOCSPGRP ioctl(2) function changes a terminal's process group (t_pgrp) to any desired value. It is typically used by csh(1) to control which set of processes (process group) is in the foreground. @.PP The vhangup(2) function is invoked by init after forking but before exec'ing getty. This function removes read and write permission for all processes (including the caller) that have the controlling terminal open (whether or not it is their controlling terminal). It then sends SIGHUP to the process group associated with the terminal (t_pgrp). The latter action is similar to the System V functionality that sends SIGHUP to a process group on death of the process group leader; 4.2BSD does not do this on the death of a process group leader. @.NH 2 4.2BSD Typical Scenario @.PP This is a typical scenario for the birth and death of a login, its controlling terminal, and process groups associated with a job. @.PP The init(8) process wants to enable a terminal for login. First it creates a new process via fork(2). Then it opens the terminal which (because the p_pgrp inherited from init is zero) causes it to become the controlling terminal for this process and either alters the process group (p_pgrp) of the process to match the terminal process group (t_pgrp) if non-zero, or alters both p_pgrp and t_pgrp to equal the process ID (pid) if t_pgrp is zero. At this point the new process has a controlling terminal whose process group (t_pgrp) is equal to the process's process group (p_pgrp). However, the process may not be a process group leader (i.e., p_pgrp may not equal pid). @.PP Now the new process calls vhangup(2) to remove access permissions for the controlling terminal from all processes (as well as sending SIGHUP to any processes in the process group previously associated with the terminal). The new process then reopens the terminal to get a file descriptor with read and write permissions since the vhangup(2) removed these permission from the file descriptor returned by the previous open. The previous file descriptor is not closed until now to prevent losing the controlling terminal; (remember that p_pgrp for the new process is no longer zero.) @.PP The new process now replaces itself with getty(8) which replaces itself with login(1) which replaces itself with a login shell, e.g., csh(1). Csh now begins to manipulate the process group associated with the terminal (t_pgrp) via the TIOCSPGRP and TIOCGPGRP ioctl(2) calls and the process group associated with its child processes (p_pgrp) via setpgrp(2) in order to allow job control. This happens (briefly) in the following way: @.PP Csh launches a pipeline by making all programs in the pipeline be immediate descendents of csh. (This is different from sh which makes all programs in the pipeline except the last be descendents of the last program in the pipeline.) All programs in the pipeline belong to the same process group (not the same as csh's process group) and the first program in the pipeline is the process group leader (its pid is equal to the process group for the pipeline). If the pipeline is being launched in the foreground (or moved to the foreground) then the process group associated with the terminal (t_pgrp) is set to the process group of the pipeline. @.PP When a logout occurs, the login shell dies. Any pending SIGTTIN, SIGTTOU, and SIGTSTP signals are cleared for all descendent processes. All immediate child processes are inherited as orphans by init; if any are currently stopped then they are killed (SIGKILL). If the exiting process is the last process that has the controlling terminal open then the terminal's process group (t_pgrp) is set to zero, otherwise it is left alone. Nothing special is done for process group leaders; in fact, login shells are frequently not process group leaders. (SIGHUP is not sent and the controlling terminal is not necessarily cleared.) @.PP Note that there may continue to be processes (previously started by the now defunct login shell) which continue to execute. And that keyboard signals can still be sent to these processes under some circumstances (specifically when the processes were in the foreground (p_pgrp == t_pgrp) when the login shell died; this usually only happens when the login shell is killed from another terminal via kill(1).) Note also that this continues to be true even after a new session logs in on the same terminal since the new login shell joins the process group which is already associated with the terminal from the prior login. @.NH 2 Job Control Signal Handling @.PP The following discussions concerning signals and kernel process synchronization are similar to ones found in [Thom78], [Ritch79], and [Bach79]. @.NH 3 Basic Overview @.PP Usually a process is either running or sleeping waiting for an event to occur (e.g., I/O completion). When a signal is sent to a process (either by another process or an I/O driver) what actually occurs is that a flag is set for the receiving (or target) process indicating that the signal has been sent and the target process performs the actual signal operation to itself the next time it runs. Thus sending a signal amounts to requesting the target process to itself perform a particular action. If the target process is already running it is interrupted to process the signal. If it is runnable but not currently running then the system merely waits for it to become the currently running process at which point the signal is acknowledged. If the target process is sleeping then either it is moved into a runnable state (if it is sleeping at an "interruptable" priority) or it is left sleeping (at an "uninterruptable" priority) and the signal is not acknowledged until the slept on event occurs. @.PP The kernel procedure which sends a signal is psignal() and is executed by the sending process or driver. Psignal() updates a list of pending signals for the receiving process. If the receiving process is the currently running process and it is executing in kernel mode then the pending signal is acknowledged when the current system call completes. (This is the case where the sending process and the receiving process are the same.) If the receiving process is the currently running process and it is executing in user mode then a special event is generated which causes the process to enter the kernel and acknowledge the pending signal. (This is the case where the sending "process" is really an interrupt handler which, for example, is servicing an interrupt character typed on a user's terminal.) If the receiving process is sleeping but not holding off signals then it is set running via wakeup(); the pending signal is acknowledged as soon as the receiving process executes. If the receiving process is suspended in a sleep state that holds off signals ("sleeping uninterruptably") then it is left sleeping; the pending signal will be acknowledged after the waited for event occurs. @.PP The procedure which tests for a pending signal is issig() and is executed by the receiving process. Issig() is executed whenever the receiving process changes from kernel mode to user mode execution; for example, at the completion of a system call. It is also executed whenever the receiving process is awakened from being suspended in a sleep state that does not hold off signals ("sleeping interruptably"). @.PP The procedure which performs the requested signal operation (e.g., invoking a signal handler or killing the process) is psig() and is executed by the receiving process if issig() returns true. @.PP This basic structure is essentially the same in System V, 4.2BSD, and HP-UX. However, under 4.2BSD-style job control, these general principles can work slightly differently: @.PP When processing stop signals, the psignal() function, called by the sending process, actually stops the target process sometimes. In these cases, the target process never realizes that it received the signal or that it stopped. However, in other cases, psignal() performs the usual process of setting the flag (p_sig) requesting that the target process stop itself the next time it runs. @.PP The issig() function, called by the target process, can actually stop the target process. @.PP The psig() function is only called in the case where a user handler has been provided for the job control signal. @.PP A more complete description of job control signal handling is contained in the pseudocode below. @.NH 3 psignal() @.PP To send SIGCONT to a target process: @.PB sending SIGCONT clears any pending stop signals; if the target process is STOPPED but is also SLEEPING (p_wchan != 0) merely continue the process's SLEEP; else if the target process is STOPPED and is NOT SLEEPING (p_wchan == 0) set the process RUNNING; @.PE @.PP To send a stop signal (SIGTSTP, SIGTTIN, SIGTTOU, SIGSTOP) to a target process: @.PB sending a stop signal clears any pending SIGCONT; if the target process is RUNNABLE or RUNNING note the pending signal in p_sig; else if the target process is SLEEPING NON-interruptably note the pending signal in p_sig; else if the target process is SLEEPING interruptably and IS catching the signal note the pending signal in p_sig and wakeup the process from its sleep; else if the target process is SLEEPING interruptably and is NOT catching the signal stop the process by setting its state to SSTOP but leave it sleeping on its p_wchan; send SIGCLD to parent (if it expects BSD-style) @.PE @.PP General note: sending a stop signal (other than SIGSTOP) to a child of init causes the target process to be killed. @.NH 3 issig() @.PP Issig() is called in all cases except where the process was sleeping interruptably and was not catching the signal. @.PP To acknowledge a pending SIGCONT or stop signal: @.PB if in the middle of a VFORK hold off all stop signals (pretend they don't exist yet) else if catching the signal return a request to invoke user signal handler via psig() else if SIGCONT do nothing /* pretend it doesn't exist */ else /* stop signals */ stop the process by setting its state to SSTOP send SIGCLD to parent (if it expects BSD-style) call swtch() to dispatch another process @.PE @.PP General note: sending a stop signal (other than SIGSTOP) to a child of init causes the target process to be killed. @.NH 3 psig() @.PP Psig() is called whenever issig() returns an indication that a user handler is defined for a job control signal. Psig() merely invokes the user signal handler. @.NH 3 wakeup() @.PP The fact that a process is sleeping (waiting for an event to occur) is indicated by two process state values: p_wchan is non-zero, indicating the event being waited for, and the process state is SSLEEP. @.PP Wakeup() usually causes all processes waiting (sleeping) on a specified event to be awakened. When a process is awakened two things happen: The process is removed from the sleep queue (p_wchan is cleared) and it is added to the run queue. @.PP If, however, wakeup() discovers a process whose p_wchan matches the specified event but whose process state is SSTOP (stopped) then the process is removed from the sleep queue (indicating that the waited for even has happened) but it is not placed on the run queue. A subsequent SIGCONT will cause it to be placed on the run queue. @.PP Thus it is possible to have a process which is both sleeping (p_wchan non-zero) and stopped (process state is SSTOP rather than SSLEEP). @.NH 3 Signal Setup via init @.PP When init(8) launches any process it causes the process to ignore all the job control stop signals (SIGTSTP, SIGTTIN, & SIGTTOU). This allows login shells which are not job control shells to automatically ignore the signals. Further, all descendent processes of such a login shell will also ignore these signals unless they explicitly enable them. @.NH 2 Foreground/Background Processes @.NH 3 Basic Overview @.PP 4.2BSD job control supports the notion of a process being in the @.I foreground or @.I background. The distinction is a background process is usually forced to stop when it attempts to perform I/O (including most control operations) on its controlling terminal, while a foreground process is not hindered. @.PP Specifically, when a background process attempts to read from its controlling terminal it is sent the SIGTTIN signal which, by default, causes it to stop. When it attempts to write to its controlling terminal and LTOSTOP has been enabled for the terminal, then the process is sent the SIGTTOU signal which, by default, causes it to stop. If, however, a background process has chosen to catch the signal, the specified user handler is invoked. If the process is ignoring or masking the stop signal(s), then the terminal I/O request returns an I/O error, EIO. @.PP A background process is one whose process group (p_pgrp) is not equal to the process group of its controlling terminal (t_pgrp) (and t_pgrp is not zero). All other processes (including ones doing I/O to terminals that are not their controlling terminals) are considered to be in the foreground. @.NH 3 Tty Driver Provisions @.PP To distinguish between foreground and background programs the tty driver must perform checks on attempted I/O operations to a process's controlling terminal. This is done in several places. @.PP At the beginning of a read/write system call the tty driver checks to see if the calling process is in the background. If it is, then all processes in the process group of the calling process are sent the appropriate signal (SIGTTIN or SIGTTOU) unless the signal is masked or ignored by the calling process. In this case the driver returns the EIO error. After the tty driver sends the signal, the calling process is put to sleep waiting for the \fIlightning bolt event\fP\(dg. @.FS \(dg The lightning bolt event is a standard @.UX event which occurs frequently, for example, every second. @.FE This allows the calling process to receive the signal (and usually stop). When the process returns from the sleep (usually by being continued) the tty driver repeats the foreground/background check before proceeding with the operation. @.PP When the process is in the foreground, the I/O operation proceeds. In the case of a terminal read this usually results in the process being put to sleep to wait for input characters to arrive. At this point the user could type their suspend character (e.g., ^Z). This causes the interrupt portion of the tty driver to send SIGTSTP to the controlling terminal's process group (i.e., all processes which are in the foreground). In our scenario this would include the process sleeping on terminal input, and this would typically cause it to stop. @.PP When a sleeping process is stopped it is also left sleeping as well. If, in this case, tty input characters subsequently arrived then the process would be awakened. However, because it is also stopped, it would not be set running; it would merely be "unslept". @.PP At some later time the process would be continued (e.g., via a csh "fg" or "bg" command which sends SIGCONT). If the process had not been previously unslept it would merely continue its sleeping; it would receive no indication that it had stopped and continued. If the process had been previously unslept it would now be set running. @.PP When the process is set running it resumes execution in the tty driver. Because a (potentially substantial) amount of time has elapsed and because the process may have been stopped and restarted, the tty driver is no longer sure whether this process is still in the foreground. So before checking if input characters are available, the tty driver rechecks whether the process is in the foreground or background. This is necessary because, in our scenario, the stopped process could have been continued in the background (via csh "bg"). To check this the tty driver merely repeats the foreground/background check it made at the beginning of the system call. @.sp 1 @.NH SYSTEM V INCOMPATIBILITIES AND THEIR RESOLUTIONS @.PP Job control as implemented in 4.2BSD is incompatible with System V semantics in some significant respects. This section discusses each of these incompatibilities and the resolution implemented in HP-UX to maintain System V compatibility. @.PP The system interface needed to support 4.2BSD-style job control, tailored for System V compatibility as discussed in this section, is presented in the form of manual page excerpts in [Len86]. @.NH 2 Setpgrp(2) Changes @.PP Because the needed semantics of 4.2BSD setpgrp(2) conflict with the semantics of System V setpgrp(2), the 4.2BSD setpgrp(2) function was renamed to be setpgrp2(2). (The choice of new name is arbitrary; setpgrp2 was chosen in the same spirit as 4.2BSD's wait3(2).) @.NH 2 SIGHUP Changes @.PP System V semantics state that when a process group leader dies, all processes in the same process group are sent the SIGHUP signal which, by default, kills all the processes. @.PP Job control shells execute a command by making all processes in the pipeline belong to the same (brand new) process group and by making the first program in the pipeline be the process group leader. Typically, the first program in a pipeline terminates before the other programs. Under System V semantics, this would cause the premature death of the remaining pipeline. Because of this, 4.2BSD does not generate SIGHUP on process group leader death. @.PP In order to support System V semantics and still allow job control to function properly, HP-UX makes a distinction between a "System V process group leader" and a "job control process group leader". A System V process group leader is given System V semantics (SIGHUP is generated) and a job control process group leader is given 4.2BSD semantics (SIGHUP is not generated). A process which becomes a process group leader via setpgrp(2) is considered to be a System V process group leader. A process which becomes a process group leader via setpgrp2(2) is considered to be a job control process group leader. Since the HP-UX (and System V) init(1M) program calls setpgrp(2) on behalf of all processes it spawns, all login shells start out as System V process group leaders. A process must explicitly call setpgrp2(2) to deviate from the System V semantics. @.NH 2 SIGCLD Changes @.PP Under System V, SIGCLD is sent to a process whenever one of its immediate child processes dies. Under 4.2BSD, SIGCLD (or its variant, SIGCHLD) is also generated when a process changes state from running to stopped. Since a System V application would not expect to receive SIGCLD under these new circumstances and since a job control shell would not be able to function properly without such notification, a compatible compromise was developed. @.PP The (parent) process wishing to trap SIGCLD may set a flag when calling the HP-UX sigvector(2)\(dg @.FS \(dg Sigvector(2) is an HP-UX extension proposed to the IEEE P1003 [Head85] which supports both the reliable signal operations of 4.2BSD sigvec(2) and the conventional signal operations of System V signal(2). In HP-UX, signal(2) is implemented as a library using sigvector(2). Note that the changes proposed here to sigvector(2) can be identically made to 4.2BSD sigvec(2). @.FE \|routine to establish a signal handler. This flag will cause SIGCLD to be sent for stopped children, in addition to terminated children. A System V application using signal(2) will see the System V compatible SIGCLD semantics. @.NH 2 Controlling Terminal Changes @.PP Under System V, whenever a process group leader dies, the controlling terminal associated with that process group (if any) is deallocated (disassociated from that process group). 4.2BSD does not deallocate controlling terminals on process group leader death for the following reason: Job control shells make the lead process in every pipeline a process group leader. If the controlling terminal for each pipeline were deallocated whenever the lead process terminated, then the remaining processes would effectively become background processes (assuming they were currently in the foreground) and would stop when any of them attempted subsequent I/O to the terminal. @.PP To allow both semantics, controlling terminals are only deallocated when a "System V process group leader" dies and not when a "job control process group leader" dies. (See the discussion of SIGHUP changes above.) @.PP However, this change leads to the following problem: In order for a terminal to be allocated as a controlling terminal for a new login, it must be deallocated when the previous login terminates. System V relies on process group leader death to deallocate controlling terminals (since all login shells are forced to be process group leaders by init(1M)). This is no longer reliable since login shells could become "job control process group leaders". Further, not all logins are spawned directly by init(1M); the 4.2BSD rlogin facility is a prime example. 4.2BSD solves this problem by allowing a new login to join the process group of the controlling terminal which is still allocated from the previous login. However this violates System V compatibility. @.PP The solution chosen was to mark a process that causes a controlling terminal to be allocated and to deallocate the controlling terminal whenever that process terminates. This reliably catches logins which are spawned either directly or indirectly from init(1M), whether they are "System V process group leaders" or not. Controlling terminals continue to be deallocated on death of System V process group leaders using the System V semantics. @.NH 2 Security @.PP Several security holes exist in the 4.2BSD process group altering mechanisms. To plug these holes the following changes were made. @.PP 4.2BSD setpgrp(2) allows a process to alter the process group associated with another process to any value. 4.2BSD restricts this operation so that the affected process must pass the same security restrictions enforced when sending signals, or must be a descendent of the calling process. However, this still allows a process to join a process group already associated with another user. To tighten this security, setpgrp2(2) was further restricted such that if the specified new process group value is equal to the process ID (pid) or process group ID of any existing processes, then all such processes must pass the above security restrictions. @.PP Similarly, the 4.2BSD TIOCSPGRP ioctl(2) allows a terminal's process group to be altered to any value. This allows a user's terminal to easily become an additional "controlling terminal" for another user's process group; keyboard signals can be sent to the other user's processes, thus bypassing the security enforced by kill(2). Because of this, the TIOCSPGRP ioctl(2) was altered to enforce similar security restrictions as setpgrp2(2). @.PP In System V and 4.2BSD, a process can obtain access to its controlling terminal by opening the file \fI/dev/tty\fP. Under System V, processes left executing after a user's logout are allowed further access to \fI/dev/tty\fP until the terminal it represents is reallocated as a controlling terminal for a new login. More specifically, \fI/dev/tty\fP access is allowed whenever the process group ID of the leftover process matches the process group ID of the terminal. These IDs continue to match immediately after logout (since both have been zeroed) until the terminal is re-enabled for login by getty(1M). (Note that when the new login terminates, \fI/dev/tty\fP access is restored again to these prior processes because the controlling terminal's process group ID is re-zeroed.) Further, if a process has its controlling terminal opened directly (not via the \fI/dev/tty\fP synonym) then access is not restricted at all after logout. These System V semantics can constitute security problems. However, they are not explicitly required by the System V Interface Definition [ATT86]. @.PP 4.2BSD does nothing to hamper \fI/dev/tty\fP access for processes remaining after logout. The process group ID for the controlling terminal is not altered, and, in fact, it is preserved even into the next login (since subsequent logins join the already existing process group associated with the terminal, if any). These semantics also represent security problems. However, 4.2BSD does prohibit access to the controlling terminal if it is opened directly; this is accomplished when init(8) issues the vhangup(2) system call. @.PP Although preserving the System V semantics for controlling terminal access after logout is not deemed necessary or even recommended, it is easy to do in the following way. Whenever a process that allocated a controlling terminal dies, all processes which share this controlling terminal have their process group ID zeroed. This is analogous to, and occurs in addition to, the System V behavior of zeroing the process group ID for all related processes when their process group leader dies. \fI/dev/tty\fP checks similar to System V can then be employed. @.NH 2 TTY Driver Considerations @.PP For System V compatibility, the suspend and delayed suspend characters are defaulted to a disabled value (0377). This means that job control is "inactive" by default when a user logs on. The user must explicitly activate job control by defining either or both of these characters via stty(1) or some similar interface. @.PP There should be no problem allowing 4.2BSD-style job control, as modified here, to co-exist with System V's shell layers job control system. (See shl(1) and sxt(7) in the System V Release 2 reference manuals.) @.sp 1 @.NH HP-UX @.NH 2 Introduction @.PP HP-UX process groups are used in two major ways. @.PP System V process groups closely resemble the concept of a login session. That is, all processes spawned during the same login session tend to belong to the same process group, and keyboard signals are typically sent to all processes spawned from the login session. @.PP Job control process groups closely resemble the concept of a task within a login session, where a task represents a set of processes which are affected as a group by job control operations. Every time a job control shell (e.g., csh) spawns either a foreground or background command, all processes in the pipeline (and their descendents) are placed in their own unique process group with the first command in the pipeline being the process group leader. @.PP A task is in the foreground when the process group associated with the controlling terminal for the task (t_pgrp) is equal to the process group associated with the processes in the task (p_pgrp). Otherwise the task is in the background. A job control shell moves a job between the foreground and background by adjusting the terminal process group (t_pgrp) of the controlling terminal. @.PP Note that a job control shell forms new process groups with process group leaders much more often than a non-job control (System V) shell usually does (every command versus every login). @.NH 2 HP-UX Process Group Handling @.PP In HP-UX, the process group associated with a process (p_pgrp) can be altered via setpgrp(2) or setpgrp2(2). @.PP As in System V, setpgrp(2) can only set the process group to equal the process ID (pid) of the process. When this happens, the resulting process with pid = p_pgrp is called a System V process group leader. @.PP Setpgrp2(2) is analogous to setpgrp(2) except that it can affect processes other than the current process and can cause the affected process to adopt a process group other than that process's process ID (pid). Setpgrp2(2) also forms job control process groups rather than System V process groups. Using setpgrp2(2), the calling process, or certain other processes, can either become a job control process group leader or can cease to be a process group leader. @.PP Because job control process groups are handled slightly differently by HP-UX than System V process groups, HP-UX marks processes that are job control process group leaders (i.e., that have called setpgrp2(2) without subsequently calling setpgrp(2)). @.PP The init(1M) process spawns all other processes on the system either directly or indirectly. Before directly spawning a process (after the fork(2) but before the exec(2)), init calls setpgrp(2). Thus all original children (not orphans) of init are forced to start out as System V process group leaders. @.PP When a new process is created, it is assigned a new pid but it inherits the process group number of its parent. Thus child processes are, by default, not process group leaders (although they can become a process group leader via either setpgrp(2) or setpgrp2(2)). @.PP When a System V process group leader that has a controlling terminal (see below) terminates, SIGHUP is sent to all processes in the same process group. Further, when a System V process group leader terminates, all processes which belong to this process group are altered to belong to no process group (their p_pgrp is set to zero). @.PP Also, whenever any process that allocated a controlling terminal terminates, all processes that share this controlling terminal are altered to belong to no process group (their p_pgrp is set to zero). @.PP When any process exits, any pending SIGTTIN, SIGTTOU, and SIGTSTP signals are cleared from all descendent processes (not just immediate children). @.NH 2 HP-UX Controlling Terminals @.PP A terminal that is currently open by a process may also be a "controlling terminal" for a process group (collection of processes). When certain control characters are typed on a controlling terminal, signals are sent by the terminal driver to all processes which belong to the process group associated with the terminal. These include the job control suspend and delayed suspend characters. @.PP Controlling terminals also play a role in determining whether a process is in the foreground or background. See FOREGROUND/BACKGROUND PROCESSES above. @.PP When a process becomes a System V process group leader (via setpgrp(2)) it automatically loses its controlling terminal. (This does not happen for a job control process group leader, i.e. when calling setpgrp2(2).) After this, the first terminal (that is not already a controlling terminal) opened by a process that is a (System V or job control) process group leader is assigned to be the controlling terminal for that process; also the process group associated with that terminal (t_pgrp) is set equal to the process group associated with the process group leader process (p_pgrp). All child processes inherit the controlling terminal and process group of their parent. @.PP More precisely, in HP-UX, the process group associated with a terminal (t_pgrp), can be changed in the following ways: @.IP (1) When a terminal is opened by a System V or job control process group leader (pid == p_pgrp) that does not already have a controlling terminal, it becomes the controlling terminal for that process group (t_pgrp is set equal to p_pgrp) if it is not already a controlling terminal. @.IP (2) When a System V process group leader (pid == p_pgrp) dies, if it has a controlling terminal that is associated with the same process group (t_pgrp == p_pgrp), that terminal is disassociated from that process group (t_pgrp is set to zero). @.IP (3) When any process dies which originally caused a controlling terminal to be created (see (1) above), if it still has a controlling terminal, that terminal is disassociated from its process group (t_pgrp is set to zero). @.IP (4) When the last process to have a terminal open closes that terminal, the terminal is disassociated from its process group (t_pgrp is set to zero). @.IP (5) The TIOCSPGRP ioctl(2) call can explicitly change a terminal's process group (t_pgrp) to any value within certain security restrictions. This is used by a job control shell to change which set of processes (process group) is in the foreground. @.NH 2 HP-UX Typical Scenario @.PP This is a typical scenario for the birth and death of a login, its controlling terminal, and the process groups associated with a job. @.PP The init(1M) process wants to enable a terminal for login. It creates a new process via fork(2) and calls setpgrp(2) to make it a System V process group leader which also removes its controlling terminal. Init then runs the getty(1M) program as the process via exec(2). Getty opens the terminal causing the terminal to become getty's controlling terminal and be associated with getty's process group (t_pgrp is set to p_pgrp). As a side effect, this process is now marked as having created a controlling terminal; when it dies the controlling terminal will be freed for re-use. Getty replaces itself with login(1) which replaces itself with a login shell. @.PP At this point one of two scenarios typically takes place. The login shell is either a job control shell (e.g., csh(1)) or it is not (e.g., sh(1)). @.PP If the login shell is not a job control shell then things proceed much as they do on System V. Usually no program calls setpgrp(2) or setpgrp2(2) and thus all descendent processes of the login shell are in the same process group and have the same controlling terminal; keyboard signals are sent to all processes launched during this session. @.PP If the login shell is a job control shell, then job control operations are performed. Csh begins to manipulate the process group associated with the terminal (t_pgrp) via the TIOCSPGRP and TIOCGPGRP ioctl(2) calls and the process group associated with its child processes (p_pgrp) via setpgrp2(2) in order to allow job control. This happens (briefly) in the following way: @.PP Csh launches a pipeline by making all programs in the pipeline be immediate descendents of csh. (This is different from sh which makes all programs in the pipeline except the last be descendents of the last program in the pipeline.) All programs in the pipeline belong to the same process group (not the same as csh's process group) and the first program in the pipeline is the process group leader (its pid is equal to the process group for the pipeline). This process is specially marked as a job control process group leader since it was established via setpgrp2(2); this basically prevents SIGHUP from being sent to the pipeline when the lead process dies. If the pipeline is being launched in the foreground (or moved to the foreground) then the process group associated with the terminal (t_pgrp) is set to the process group of the pipeline via the TIOCSPGRP ioctl(2). @.PP When a logout occurs, the login shell dies. Any pending SIGTTIN, SIGTTOU, and SIGTSTP signals are cleared for all descendent processes. All immediate child processes are inherited as orphans by init; if any are currently stopped then they are killed (SIGKILL). Since the login shell (actually the getty before it was overlaid) created a controlling terminal, the controlling terminal is now freed (t_pgrp is set to zero) so that it can be claimed as a controlling terminal by a subsequent getty respawned by init; also, all processes which share this controlling terminal have their process group (p_pgrp) set to zero. @.PP When a logout occurs and the login shell is a System V process group leader, SIGHUP is sent to all processes in the same process group, and the process group (p_pgrp) of all descendent processes is set to zero. @.PP Note that there may continue to be background processes (previously started by the now defunct login shell) which continue to execute but keyboard signals will no longer be sent to these processes (since both t_pgrp and p_pgrp equal zero). @.sp 1 @.NH ACKNOWLEDGEMENTS @.PP The following people from Hewlett-Packard contributed to the interface design and implementation of job control for HP-UX: Jim Barton, Dave Decot, Larry Dwyer, Jeff Glasson, Rita Hanson, Stephen Hares, Steve Head, Bob Lenk, John Marvin, Dave Mears, Peter Notess, Arn Schaeffer, Eviatar Shafrir. @.PP Guy Harris from Sun Microsystems made many substantive comments and suggestions which contributed to the interface design and to this paper. @.sp 3 @.NH REFERENCES @.IP [ATT86] 15 \fISystem V Interface Definition\fP, Issue 2, AT&T, 1986. @.IP [Bach84] M. J. Bach and S. J. Buroff, "Multiprocessor @.UX Operating Systems", \fIAT&T Bell Lab. Tech. J.\fP, \fB63,\fP No. 8 (October 1984), pp. 1733-1749. @.IP [Head85] Stephen Head and Donn Terry, "Reliable Signals Proposal", IEEE P1003 Proposal #P.042, Hewlett-Packard Co., September 11, 1985. @.IP [Joy80] William Joy, "An Introduction to the C Shell", Computer Science Division, University of California at Berkeley, November 1980. @.IP [Len86] David Lennert, Guy Harris, et. al., "System V Compatible BSD-style Job Control Facilities", IEEE P1003 Proposal #P.047, Hewlett-Packard Co. & Sun Microsystems, April 9, 1986. @.IP [Ritch79] Dennis M. Ritchie, "The @.UX I/O System", \fIUNIX Programmer's Manual\fP, Seventh Edition, Volume 2b, Bell Telephone Laboratories, Murray Hill, NJ, January 1979. @.IP [Roch85] Marc J. Rochkind, \fIAdvanced @.UX Programming\fP, Englewood Cliffs, N.J.: Prentice-Hall, 1985. @.IP [Harris86] Guy Harris, "Notes on Signal, Terminal Interface, and User/Group ID Handling Proposals", IEEE P1003 Proposal #P.045, Sun Microsystems, January 11, 1986. @.IP [Thom78] K. Thompson, "UNIX Implementation", \fIBell System Tech. J.\fP, \fB57,\fP No. 6 (July - August 1978), pp. 1931-1946. @.IP [UCB83] \fIUNIX Programmer's Manual\fP, 4.2 Berkeley Software Distribution, Virtual VAX-11 Version, Computer Science Division, University of California at Berkeley, August 1983. @.PP @.bp @.DS C APPENDIX JOB CONTROL MANUAL PAGE EXCERPTS @.DE @.PP The following pages contain the @.UX manual pages which are effected by adding a System V compatible implementation of 4.2BSD job control. Note that each manual page generally contains only that portion of text which differs from the System V manual page of the same name. Sometimes, unchanged text is provided for locality reference. Changed text lines are flagged with change bars. @//E*O*F jobpaper.ms// chmod u=rw,g=rw,o=r jobpaper.ms echo Inspecting for damage in transit... temp=/tmp/shar$$; dtemp=/tmp/.shar$$ trap "rm -f $temp $dtemp; exit" 0 1 2 3 15 cat > $temp <<\!!! 1200 8792 53685 jobpaper.ms !!! wc jobpaper.ms | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp if [ -s $dtemp ] then echo "Ouch [diff of wc output]:" ; cat $dtemp else echo "No problems found." fi exit 0 Volume-Number: Volume 7, Number 54