A program started directly in a terminal has its stdin, stdout and stderr connected to the special file associated with the terminal, e.g. to /dev/ttyN
or /dev/pts/N
(where N
is a number). Even without knowing the pathname of this file, each program can use /dev/tty
to communicate with its controlling terminal. (In general there may be no controlling terminal, but for a program started directly in a terminal there obviously is.)
By "started directly in a terminal" I mean e.g. just after logging in to a virtual console (the textual interface after e.g. Ctrl+Alt+F4 or so), just after starting a virtual terminal like konsole
(in a GUI), just after starting a terminal multiplexer like tmux
(under whatever terminal) or just after logging in via SSH.
The program started directly is usually a shell running in its interactive mode. It can start other programs whose stdin, stdout and stderr may be redirected away from the terminal (and e.g. they may form a pipe).
Reading from /dev/tty
(or from /dev/ttyN
or /dev/pts/N
) means reading from keyboard or so. Writing to /dev/tty
(or …) usually causes text to appear in the actual terminal. Between the keyboard and the program there are several layers, one of them is called line discipline. This is the layer that makes Ctrl+Z special.
When you press Ctrl+Z trying to stop a process in a terminal, the information getting to the line discipline is just one byte denoted ^Z
(hex 0x1a
, octal 032
, ASCII SUB). I mean this is what konsole
or other terminal emulator generates upon Ctrl+Z (unless you break its configuration or let unfortunate global hotkeys interfere), or this is what PuTTY sends over network upon Ctrl+Z. The line discipline may or may not intercept this byte.
If the line discipline does not intercept ^Z
then the byte will appear to a process reading from the terminal simply as the ^Z
byte it is (like e.g. a
is normally transmitted as a
, b
as b
etc.). If the line discipline intercepts ^Z
then this is where the magic begins.
The line discipline can be configured with stty
(or directly with respective ioctl system calls). See man 1 stty
and run stty -a
to see the settings. Actually sole stty -a
is not a good command to see the settings at arbitrary moment, because it can only show you the settings when stty
itself runs; the settings when the shell awaits your input or when any other program runs in the foreground may be quite different. To see the current settings of /dev/pts/N
invoke </dev/pts/N stty -a
in another terminal. Shells commonly change some of these settings back and forth; various programs also configure the terminal to their needs.
The setting responsible for stopping a program is susp
, usually it's susp = ^Z
in the output of stty -a
; also isig
must be enabled (i.e. isig
, not -isig
in the output of stty -a
). ^Z
is special then. If you invoke e.g. stty susp ^N
(with literal ^N
, the tool will understand you mean one-byte ^N
) then ^N
will become special and you will be able to stop processes with Ctrl+N instead of Ctrl+Z.
Stopping a program involves a signal. With susp = ^Z
and isig
, a ^Z
byte getting to the line discipline makes the kernel send SIGTSTP to the foreground process group of the respective terminal. The byte gets "converted" to the signal, it is not transmitted as ^Z
to any program reading from the terminal.
The foreground process group is a concept that allows moving processes (actually process groups) from the foreground to the background and back, while retaining their ability to interact with the terminal when in foreground, and (partially) preventing them from doing so when in background. The idea is the kernel knows which process group shall be considered "in the foreground" for which terminal at any given time. It's usually the interactive shell who informs the kernel about changes (in Linux see man 3 tcsetpgrp
, man 2 setpgid
).
It works like this: a sophisticated enough shell (with job control enabled, which is the default state for interactive shells) is in a process group which initially is the foreground process group for the controlling terminal of the shell. When the shell runs a command or a pipeline, it puts the new process(es) in a separate process group. Now:
If the command (or pipeline) is terminated by
&
then the shell will not set the foreground process group to the new group, it will not wait for the process(es) to finish. In effect the process(es) will run asynchronously with the shell and the kernel will still consider the shell's group to be in the foreground. The shell can then interact with the terminal right away, but the process(es) not so much (I will elaborate in a moment).If the command (or pipeline) is terminated by
;
or by a newline then the shell will set the foreground process group to the new group and it will wait for the process(es) to finish. In effect the process(es) will run synchronously and the kernel will consider their process group to be in the foreground. The process(es) can then interact with the terminal, but the shell itself shall just sit there and wait.
In the latter case the kernel knows that SIGTSTP (resulting from Ctrl+Z transmitted as ^Z
and converted because of susp = ^Z
) should get to the process(es).
A process may ignore, block or handle the signal, or (the default behavior) it may be stopped by the signal.
When a process changes state (e.g. exits or gets stopped) then its parent gets notified with SIGCHLD. In our case if Ctrl+Z stops the process(es) then the interactive shell that sits there and waits will get SIGCHLD and it will check which of its children have died, which have stopped; then it will act accordingly: usually it will put itself in the foreground (i.e. set the foreground process group back to its own process group) and give you a prompt.
Shell builtins bg
and fg
send SIGCONT to the process(es) of a (possibly stopped) job, this signal makes the process(es) continue their operations. bg
does not set the foreground process group to their process group and does not make the shell wait, the job will continue in the background; fg
does set the foreground process group to their process group and makes the shell (now not in the foreground process group) wait, the job will continue in the foreground.
Being in the foreground or in the background consists of two things:
Synchronism: the parent interactive shell will wait for a foreground command (or pipeline) and it will not wait for background jobs (unless told to
wait
).Being or not being in the process group that is set as the foreground process group at the moment.
Note in general a parent process may or may not wait for its child, regardless if they are both in the foreground process group or not, or in different groups. This means the above two things are independent. A parent process (especially if it's a shell that implements job control) has to deliberately manage things in a way that makes good sense.
Being or not being in the foreground process group has the following consequences (at least in Linux):
Only processes in the foreground process group will receive interrupts from the keyboard: SIGINT, SIGTSTP and SIGQUIT. Usually these are from Ctrl+Z, Ctrl+C and Ctrl+\ respectively, transmitted respectively as
^C
,^Z
and^\
, until they are converted to respective signals by the line discipline due tointr = ^C
,susp = ^Z
andquit = ^\
settings whenisig
is enabled.A process not in the foreground process group for its controlling terminal cannot read from its controlling terminal. If such process tries to do it then it will receive SIGTTIN and (by default) it will be stopped. The process may ignore, block or handle the signal, still it won't be able to read from the terminal until its group becomes the foreground process group.
A process not in the foreground process group for its controlling terminal may or may not get SIGTTOU (and be stopped by default) when it tries to write to its controlling terminal. SIGTTOU will be sent, unless
tostop
is disabled (stty -a
shows-tostop
, nottostop
) or the process ignores the signal; so this can be configured. By ignoring or handling the signal, the process may avoid being stopped and actually write to the terminal, access will be granted.A process not in the foreground process group for its controlling terminal may or may not get SIGTTOU (and be stopped by default) when it tries to reconfigure its controlling terminal. SIGTTOU will be sent, unless the process ignores the signal (the state of
tostop
does not matter). By ignoring or handling the signal, the process may avoid being stopped and actually reconfigure the terminal, access will be granted.
This was designed to prevent background processes from stealing input and getting interrupts from the keyboard, while still keeping them connected to the terminal in case they get moved to the foreground; and also to help processes be non-itrusive when it comes to reconfiguring the controlling terminal from the background, while still giving them an opt-in way to do so if they really want. It works this way only for the controlling terminal; I mean the process can read from or write to other terminals just like from or to other files (i.e. according to their ownership and mode).
Notes, trivia:
- In case of interacting with
tmux
, there is an "outer" tty the client uses and inner ttys (one for each pane) for programs running under tmux server.tmux
(the client) configures the "outer" tty to-isig
, so Ctrl+Z does not result in SIGTSTP sent to the tmux client, but gets to it (via stdin) as^Z
that may get to the inner tty and may result in SIGTSTP sent to process(es) running under the tmux server. - Similarly in case of
ssh
there is a tty the client uses on its side and there may (or may not) be a tty (set up bysshd
on the remote side) the invoked program(s) (a shell or whatever) use. An additional line discipline is useful in some cases, an obstacle in other cases. Compare ssh with separate stdin, stdout, stderr AND tty. - When there is an additional line discipline, depending on which one (if any) converts
^Z
to a signal, you get different results. See How is Ctrl+c or Ctrl+z sent over SSH?