Pseudo-terminals are a bi-directional communication channel that can be used to connect two processes (or more correctly, a process group to another process). You may (correctly) ask why two pipes would not do the same thing; the answer is that a lot of modern UNIX software relies on the way the terminal interface works, and thus would malfunction when presented with a pipe as standard input. What PTYs provide is a lot like two pipes, but with a TTY interface.
PTYs can be used in a number of important and exciting applications, such as windowing systems and 'script-driven' interfaces.
Windowing systems like the UNIX X windowing system (known as just “X”) use PTYs to give a process group an interface that looks exactly like a real terminal; however, the 'terminal' in this case is actually a window in a graphics-based system. The program that manages the window ('xterm' in X) is called the master. It is responsible for setting up the PTY, and starting up the process with redirection (usually a shell) that is to run in the window. The process running in the window is called the slave.
To allocate a PTY, the master opens in turn each PTY device starting with .ptyq0. If a PTY is already in use, the open call will return an error (the kernel uses the EXCL flag internally). When an open succeeds, the master then has exclusive access to that PTY. At this point, the master opens the corresponding TTY file (.ttyq0 — .ttyqf), or the slave device. It then forks off a process, which sets redirection up in the normal fashion and then exec's the program to run on the PTY.
The following code fragment is taken from the source code for the Graphical Shell Interface (GSI) NDA. initPipe scans the PTY devices, looking for a free one as discussed above. Note that the master side of a PTY does not have (by default) a terminal interface; it is a very raw device, with only a few ioctl's to be able to send signals and handle other such low-level tasks.
char buffer[1024]; int ptyno, master; int initPipe(void) { int cl[2]; struct sgttyb sb; char *ptyname = ".ptyq0"; unsigned i; /* We have to open the master first */ for (i = 0; i<2; i++) { /* generate a PTY name from the index */ ptyname[5] = intToHex(i); master = open(ptyname,O_RDWR); if (master > 0) { break; /* successful open */ } } if (master < 1) { return -1; } ptyno = i; pid1 = fork(producer); return 0; }
producer() sets up redirection for the shell, and also opens the slave side of the PTY. The slave processes must not have any access whatsoever to the master side of the PTY, so close(0) is used to close all open files (which includes, at this point, the master PTY file descriptor from initPipe). Note that as in many pipe applications, the file descriptor that will be assigned to a newly opened file is assumed, and that can be safely done in this case because it is clear that with no files open the next file descriptor will be 1.
/* the shell is executed here */ #pragma databank 1 void producer(void) { char *ptyname = ".ttyq0"; /* we must not have access to ANY other ttys */ close(0); /* close ALL open files */ ptyname[5] = intToHex(ptyno); /* modify the tty slave name to correspond * to the master */ slave = open(ptyname,O_RDWR); /* file descriptor 1 */ dup(slave); /* fd 2 */ dup(slave); /* fd 3 */ /* Set up the TextTools redirection */ SetOutputDevice(3,2l); SetErrorDevice(3,3l); SetInputDevice(3,1l); WriteCString("Welcome to GNO GSI\r\n"); _execve(":bin:gsh","gsh -f"); /* If we get here, we were unable to run * the shell. * * GDR note: printf should not be used here, * since we're in the child process */ printf("Could not locate :bin:gsh : %d", errno); } #pragma databank 0
consume() is called as part of GSI's event loop. It simply checks to see if there is any data for the master by using the FIONREAD ioctl, one of the few ioctl's supported by the master side. See PTY(4) for details. Any data that is available is sent to the window via a routine toOut, which inserts the new data into a TextEdit record.
void consume(CtlRecHndl teH) { char ch; int fio, fio1, i; ioctl(master,FIONREAD,&fio); if (fio) { if (fio > 256) { fio = 256; } fio1 = read(master,buffer,fio); buffer[fio] = 0; toOut(buffer,fio,teH); updateWind1(fio,fio1); } }
When the user types a key, the keypress is sent to the slave by simply writing the data with a write call.
void writedata(char k) { write(master, &k, 1); }
When the user is done with the window and closes it, GSI closes the master end of the PTY.
void closePipe(void) { int cl[2]; close(master); }
When this is done, the slave process receives a SIGHUP signal, to indicate that the connection was lost. Since the standard behavior of SIGHUP is to terminate the process, the slave dies and either the slave or the kernel closes the slave end. At this point, the PTY is available for re-use by another application.
As you can see, PTYs are very simple to program and use. The simplicity can be misleading, for PTYs are a very powerful method of IPC. As another example of the use of PTYs, we point out that PTYs can be used to drive programs with 'scripts'. These scripts are a series of 'wait-for' and 'print' operations, much like auto-logon macros in communications programs such as ProTERM. Script-driving a program can be used to automate testing or use of an application.
PTYs can be used to test software that would normally work over a regular terminal (such as a modem). Since PTYs are identical (to the slave) to terminals, the application being tested doesn't know the difference. What this means to the programmer is incredible power and flexibility in testing the application. For example, a communications program could be nearly completely tested without ever dialing to another computer with a modem!
There are so many applications of PTYs that to attempt to discuss them all here would be impossible; as PTYs are discovered by more GNO/ME programmers we expect that more useful PTY applications will become available.
Feedback