1. Introduction

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

Here, we used ps to show the hierarchy (-H) around a given TTY (-t tty1). Note how it started with a login shell, but transitioned to a traditional bash shell as a child process.

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

Just like getty, agetty is responsible for TTY maintenance.

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.

5. Summary

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.