I have always found the details of the relationships between terminals, standard out/in, and pseudo terminals on Unix variants a bit confusing. This post is my attempt to get some things straight. It may be desultory, over simplified, and at times, blatantly incorrect. Please let me know if I have butchered anything. I will post links as I go along and at the end that may be more enlightening. Please note, I am on OS X, a bsd variant.
Terminal Emulator and pseudo terminals
First, assuming you are running something like Mac OS X Terminal.app, you are running a terminal emulator. This much is pretty straight-forward. This is an application that emulates in software what used to be a hardware VT-something terminal. You are also running a shell application, possibly bash. Since, in a current day system, both the shell and the Terminal.app are processes running on the machine, rather than the shell outputing to a hardware device, there needs to be some way for the shell to communicate with the Terminal emulator. This is where the Kernel steps in to help out in the form of pseudo-terminals.
If, at your terminal, you type:
tty
The sytstem will respond with something like
/dev/ttys001
This is the slave end of your current pseudo-terminal. The slave end is attached to your shell. You also have an associated master side of your pseudo-terminal located at something like:
/dev/ptys1
This is attached to the terminal emulator (Terminal.app)
From the BSD man pages:
"The slave device provides to a process an interface identical to that described in tty(4). However, whereas all other devices which provide the interface described in tty(4) have a hardware device of some sort behind them, the slave device has, instead, another process manipulating it through the master half of the pseudo terminal. That is, anything written on the master device is given to the slave device as input and anything written on the slave device is presented as input on the master device."
From Wikipedia:
"Important applications of pseudo terminals include xterm and similar terminal emulators in the X Window System and other window systems like Terminal application in Mac OS X, in which the terminal emulator process is associated with the master device and the shell is associated with the slave. Any terminal operations performed by the shell in a terminal emulator session are received and handled by the terminal emulator process itself (such as terminal resizing or terminal resets). The terminal emulator process receives input from the keyboard and mouse using windowing events, and is thus able to transmit these characters to the shell, giving the shell the appearance of the terminal emulator being an underlying hardware object."
I believe that /dev/tty is a shortcut to the current slave device (the one returned by the tty command). Officially, it is " a synonym for the controlling terminal of a process, if any" (see http://stackoverflow.com/questions/4667154/what-is-the-difference-between-writing-to-stdout-and-a-filehandle-opened-to-dev). In fact, if you do the following:
echo "HI" > /dev/tty
you should see "HI" printed to your terminal. This makes sense because the slave device is connected to the master device, which Terminal.app is attached to. Your bash shell will associate its stdout (file descriptor 1) with the device at /dev/tty. "Any process it spawns without redirecting stdout will also have this association" (from http://stackoverflow.com/questions/4667154/what-is-the-difference-between-writing-to-stdout-and-a-filehandle-opened-to-dev).
Additionally, on OS X, /dev/fd/ will contain your file descriptors for the current process, and there are also shortcuts at /dev/stdin, /dev/stdout, and /dev/stderr.
Session
The session in the container for all the processes that will be associated with a controlling terminal (tty). Each session has one controlling terminal, and vice versa.
Session Leader
Your shell (or the login process that created the shell) in the above situation would be considered the session leader, because it was the initial process of the session and is interacting with the controlling terminal.
From http://www.gnu.org/s/hello/manual/libc/Concepts-of-Job-Control.html
"Usually, new sessions are created by the system login program, and the session leader is the process running the user's login shell."
Process Groups/Jobs
When you execute something from the shell, this is called a command. The command may actually launch more than one process, for example, if you use the pipe (|) to string together a few unix commands. These processes will all belong to the same process group (also called a Job). Process groups are actually what receive signals, not individual processes. All process groups launched from the session leader are in the same session. When the process gets created, it will actually be in the same process group as its parent as well, but as part of standard operating procedure, the shell will immediately set the process to its own unique process group. Processes all inherit the same controlling terminal, stdin and stdout of the session leader (and are called child processes). These process groups are job controlled by the shell (the shell can start, stop, and suspend, background and foreground them).
See http://www.gnu.org/s/hello/manual/libc/Concepts-of-Job-Control.html for a more indepth explanation of the Session->Process Group->Process relationship.
The shell (session leader) is in charge of job control. It must decide which of the process groups it has started will have access to the controlling terminal. This is accomplished by setting the "foreground process-group of the controlling terminal". The reason this distinction must be made is that it needs to be clear which process group should be receiving input from the keyboard and to a lesser extent, which should be allowed to print to the terminal ( I say to a lesser extent because I think on most systems, by default, more than one process group can try to write to the controlling terminal, and it will interleave the output).
As an example, if you run
ps -la
you will see all the processes running (that are associated with a controlling terminal, I believe GUI processes on systems like OS X don't really play by these rules, so you won't see them). If you look in the STAT column you will see some letters. Capital S means the process is sleeping, R means it is running. The second character signifies additional state. s means the process is the session leader, and a + means the process is currently in the foreground process group of its controlling terminal. If you are only running one Terminal, you should see that there is a login process that is the session leader, that the bash process is sleeping, and that the ps process is running and in the foreground process group(R+). You can see in the TTY column what the controlling terminal is for each process, and in this case they will all be the same. This is the same device that would be returned by the "tty" command.
Just for fun, if you open another terminal, and type ps -la, and look at the bash process on the controlling terminal of your previous terminal, you will see that is now has a + sign associated with it, since the ps process has ended, and the shell itself is now in the foreground process of the controlling terminal again.
So, as you are probably aware, running a process with an & after it from the shell will put that process in the background, and return control of the terminal to the shell. The process can probably still write to the terminal, which might cause output to be annoyingly interleaved with what you are trying to do on the shell, but if it tries to read from its stdin, which is the controlling terminal, it will receive a SIGTTIN signal and will be stopped.
See these links:
http://www.gnu.org/s/hello/manual/libc/Concepts-of-Job-Control.html
http://www.gnu.org/s/hello/manual/libc/Access-to-the-Terminal.html#Access-to-the-Terminal
http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap11.html
Creating daemons
With this information in hand, we can understand what it takes to create a process that will not die once we log off our shell. First, we need to redirect its standard in, standard out, and standard error to a place that will exist once we log off, because the controlling terminal will be gone. Often this is either a log file or /dev/null.
When we log out, the controlling terminal will be destroyed, and the shell will receive the SIGHUP signal, which it then sends to all its child processes (type "jobs" at the command line to see who would receive SIGHUP if your terminal closed, these are called processes that are "under job control" of the session leader). To prevent our daemon process from receiving this signal, you can either initially execute it wrapped in the "nohup" command, or if you decide you need to do it after the fact, you can use the "disown" command.
Additional Links
http://en.wikipedia.org/wiki/Zombie_process
http://blog.nelhage.com/tag/termios/
http://rachid.koucha.free.fr/tech_corner/pty_pdip.html
http://www.linusakesson.net/programming/tty/index.php
https://github.com/nelhage/reptyr
Thanks for reading, please leave any comments or corrections.