Terminals sometimes break so badly that they need to be killed and respawned to continue functioning properly. However, doing so may pose some challenges, especially when dealing with a TTY instead of a PTY.
In this tutorial, we discuss killing a TTY along with its accompanying shell. First, we look at the process hierarchy around a terminal. Next, we follow the lifecycle of a TTY. Finally, we apply this knowledge to find ways of killing a given terminal, effectively restarting it via its kernel management.
For brevity, in this tutorial, we:
- use the terms terminal and TTY interchangeably
- minimize the output of some commands to the useful essentials
We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.
2. TTY Process Hierarchy
In older systems, all TTYs are already allocated during initialization. Newer systemd Linux versions usually have a dynamic service responsible for spawning a TTY on demand.
Either way, we can get a terminal via any of these means. To check the current TTY, we can use the tty command:
$ tty /dev/tty1
Since a TTY just connects the user, an input device (e.g., a keyboard), and an output device (e.g., a monitor), it’s not very useful on its own. Thus, we often attach shells to terminals.
In most settings, each user logs in via their configured shell or a common default login shell. Importantly, the terminal and the shell have different functions, but they are linked:
$ ps -H -t /dev/tty1 PID TTY CMD 666 tty1 login 667 tty1 bash
On the other hand, we have terminals that have not gone through the usual login process:
$ ps -H -t /dev/tty2 PID TTY CMD 666 tty2 agetty
Another common scenario is to run a graphical user interface (GUI) like the X Window system directly:
$ ps -H -t /dev/tty7 PID TTY CMD 566 tty7 Xorg
Of course, a GUI such as Xorg often provides its own login means.
After identifying a terminal and its hierarchy, let’s see the TTY lifecycle.
3. Terminal Lifecycle
As we saw, there are several processes associated with a TTY.
Let’s see a real-world example by booting into Linux and logging in at the text terminal and checking our current TTY and its process tree:
baeldung login: user Password: $ tty /dev/tty1 $ ps -H -t /dev/tty1 PID TTY CMD 166 tty1 login 167 tty1 bash
Now, we can do a test exiting the shell, bash, PID 167:
$ kill -SIGKILL 167 baeldung login:
After terminating the main shell process, our terminal seems to revert to the login shell, as expected. To confirm, let’s switch to /dev/tty2 and check the process tree for /dev/tty1:
$ tty /dev/tty2 $ ps -H -t /dev/tty1 PID TTY CMD 666 tty1 agetty
Perhaps a bit unexpected, but we now see agetty as the only process (PID 666), linked with our initial TTY. To explain why that is, we can do another unexpected action and enter the wrong credentials at the login prompt of /dev/tty1:
baeldung login: baduser Password: Login incorrect baeldung login:
Going back to /dev/tty2, let’s check the process tree of /dev/tty1 again:
$ tty /dev/tty2 $ ps -H -t /dev/tty1 PID TTY CMD 666 tty1 login
Now, we see login process, but with the same PID (666) as the responsible agetty instance. In essence, after the first login attempt, the terminal manager (here, agetty) overlays itself with the login shell.
Importantly, multiple incorrect login attempts or a timeout will terminate login, reverting to a fresh instance of agetty with a different PID. This convoluted process originates from older times, where the transition between the login shell and the terminal manager causes a connection teardown and prevents brute-force password guessing.
For our needs, we can note that a terminal is either managed by the kernel or a login shell. The latter can be a GUI like xorg or text-based like login. This is vital for our attempts to kill the TTY.
4. Killing the TTY
Of course, killing a TTY is done with kill or pkill.
Terminals stay alive only because of their initial process. Killing that process restarts the management for the TTY from the kernel, restarting the terminal itself:
$ tty /dev/tty2 $ ps -H -t /dev/tty1 PID TTY CMD 166 tty1 login 167 tty1 bash $ kill -SIGKILL 167 $ $ ps -H -t /dev/tty1 PID TTY TIME CMD 666 tty1 00:00:00 agetty
After using the kill command from /dev/tty2 to stop the shell of /dev/tty1, the latter terminal reverts to agetty, as we saw. It’s good practice to send SIGHUP for normal termination down the process tree, but SIGKILL has the most guarantees.
An even easier way to achieve this without checking the process tree first is with pkill from /dev/tty2:
$ ps -H -t /dev/tty1 PID TTY CMD 10166 tty1 login 10167 tty1 bash 10266 tty1 sleep 666 $ pkill -SIGKILL -t tty1 $ ps -H -t /dev/tty1 PID TTY CMD 10666 tty1 agetty
Here, the first argument to pkill is the same as for kill above. Similarly, the second argument (-t) is like the one for ps, but we only supply the TTY name (tty1), not its full path (/dev/tty1).
Terminating xorg or any other GUI shell, if it’s the terminal’s root process, would again reset the TTY to agetty in our case or the terminal manager in general.
Finally, we can always kill agetty itself, but that usually just produces a new instance of the same since the kernel is responsible for reviving terminal management services.
In this article, we discussed what comprises a TTY, how it comes to life, and how we can dispose of one.
In conclusion, killing the root process of a TTY forces a complete restart of the kernel’s management over that terminal.