What is the terminal?
As I first started using Linux, the terminal was “just there”, it was a given. Need to run some commands? Open the terminal and type away.
Think about it for a second. You clicked on an icon or pressed some hotkey, and a new window appears on your screen: A familiar prompt, maybe preceded by a MOTD, and you can start processes, read their output, move around in the filesystem etc.
- What just happened?
- How does this new window know how to interpret a CTRL+C?
- Where are you actually typing?
Glossary#
Lets start with a couple of definitions, in some circumstances these terms are used interchangeably but it is important to clarify the differences
terminal: Broad term used to describe the whole interface used to input commands
terminal emulator: Executable, like xterm
, alacritty
, ghostty
or the GNOME terminal
TTY: TeleTYpe subsystem
of the Linux kernel
tty (1): Executable, prints the file name of the controlling terminal to stdout, part of
GNU coreutils
virtual TTY: Character device, usually living in /dev
, eg:/dev/tty1
PTY: PseudoTerminal / PseudoTTY, are pairs of pseudodevices file, eg /dev/pts/1
(slave) and
/dev/ptmx
(master)
shell: Executable, command line interpreter. Takes input from a user and execute programs,
scripts, manage files etc.
bash: One of the most used shells
A couple more important terms#
PID: Process ID
PPID: Parent Process ID (PID of the parent process)
syscall / system call: Interface between userland and kernel,
more info here
Launching a terminal emulator#
Every Linux system with a GUI I ever used comes with a terminal emulator.
To run it, some interaction with the desktop environment
is needed
On my system I can simply click the “Application Menu” and select “Terminal Emulator” to start the
xfce4-terminal
Mouse clicks#
Lets focus on those two clicks.
The desktop environment knows nothing about you hardware, it’s the
kernel’s job (if the right drivers
are available) to interpret the raw inputs from your mouse and generate events for processes in
userland.
Those events are picked up by the Xorg server (a process running in userland) and dispatched to the appropriate application (X client) using the X11 protocol
Wayland: exists (modern alternative to Xorg/X11)
Launching a program#
The “Terminal Emulator” icon / menu entry is part of the xfce4 desktop environment, more precisely the xfce4-panel process: This is the X11 client alerted by Xorg when the mouse is clicked
On my system, the PID of xfce4-panel is 2441:
$ ps -ef | grep xfce4-panel
fbtd 2441 2301 0 Aug24 ? 00:04:42 xfce4-panel
fbtd 1268092 3864 0 19:49 pts/8 00:00:00 grep --color=auto xfce4-panel
stracing#
Feel free to skip this section if you are not interested in the underlying syscalls to spawn new processes
The strace (1) utility can show us what is going on within the xface4-panel process as we click “Terminal Emulator”, in particular which syscalls are being invoked to spawn new processes.
Arguments to strace:
--follow-forks
Also track child processes-p 2441
Attach to the xfce4-panel process-e trace=%process
syscalls to trace: We are only interested in processes related syscalls
Some lines, for example failed syscalls, have been omitted for clarity
0$ strace --follow-forks -p 2441 -e trace=%process
1strace: Process 2441 attached with 20 threads
2[pid 2441] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 1276856 attached , child_tidptr=0x7fd51a017610) = 1276856
3[pid 1276856] execve("/usr/bin/exo-open", ["exo-open", "--launch", "TerminalEmulator"], 0x55b808f4d5b0 /* 33 vars */) = 0
4
5[pid 1276856] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f565d059d10) = 1276857
6[pid 1276857] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f565d059d10) = 1276858
7[pid 1276858] execve("/usr/bin/xfce4-mime-helper", ["/usr/bin/xfce4-mime-helper", "--launch", "TerminalEmulator"], 0x5645e2842400 /* 33 vars */ <unfinished ...>
8[pid 1276858] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 1276859 attached , child_tidptr=0x7f8253e19d90) = 1276859
9[pid 1276859] execve("/usr/bin/x-terminal-emulator", ["/usr/bin/x-terminal-emulator"], 0x5601f78aa410 /* 33 vars */) = 0
10[pid 1276859] execve("/usr/bin/xfce4-terminal", ["xfce4-terminal"], 0x55cf733433b0 /* 33 vars */) = 0
11
12[pid 1276859] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fc604c54fd0) = 1276879
13[pid 1276879] execve("/bin/bash", ["bash"], 0x55ef77e99110 /* 36 vars */) = 0
Using the typical fork/exec technique, and a couple intermediary processes we end up with two new processes:
- line 10: PID 1276859 as the terminal emulator itself (xfce4-terminal)
- line 13: PID 1276879 as a shell (bash)
We can verify the PIDs with a couple of ps
commands from within the new terminal window:
$ ps -f -p "$$"
UID PID PPID C STIME TTY TIME CMD
fbtd 1276879 1276859 0 20:35 pts/5 00:00:00 bash
$ ps -f 1276859
UID PID PPID C STIME TTY STAT TIME CMD
fbtd 1276859 1 1 20:35 ? Sl 0:00 xfce4-terminal
The emulator and the shell#
Our new “Terminal” is made up of these two very important processes, lets see what are their roles
Terminal Emulator#
xfce4-terminal in our case
- Emulate a physical terminal, like the VT100
- Handle inputs (keyboard / mouse events) from the X server
- Display output in its window
- Interpret ANSI escape codes
Many fancy terminals provides more features, like a semi transparent background, tabs, clipboards etc
Shell#
It is called a shell, because it surrounds the kernel.
On my system the default shell is bash
- Interpret user input and commands
- Provide a CLI
- Start processes by forking / exec-ing as needed
- Handle working directory context
- Offer some builtin commands to manipulate its own environment
IPC: InterProcess Communication#
There are many ways for processes to communicate between each other:
pipes,
sockets,
shared memory, etc.
The main IPC between terminal emulator and shell takes place on a pseudoterminal (PTY).
More precisely, a PTY pair:
- Master,
/dev/ptmx
, created by the terminal emulator with a regularopen (2)
syscall - Slave,
/dev/pts/5
, obtained by callingptsname(3)
on the master. The shell opens it three times: as stdin, stdout and stderr
we can verify which PTY the terminal is using by runningtty
without any arguments - The kernel takes care of transmitting data between master and slave
Here is the situation so far:
Legend:
Pseudotreminals#
Why not use a simple pipe between terminal emulator and shell?
Here are some of the feature provided by pseudoterminals:
- echoing: what you type on the master side ( when you use the
write
syscall) is echoed back ( when you use theread
syscall), line 10 below - signal generation: interpret special character in order to generate signals for the shell,
eg CTRL+Z sends the
SIGSTOP
signal to the shell, line 4 below (if using bash, the process can be resumed withfg
) - line buffering
- pauses and resume input/output
Line Discipline#
Line discipline is the kernel component that handles the communication between master and slave.
It can be configured at runtime with tools like stty (1)
, here is the list of settings:
line breaks added and some line omitted for clarity
0$ stty -a
1speed 38400 baud; rows 24; columns 80; line = 0;
2intr = ^C;
3quit = ^\;
4susp = ^Z;
5...
6isig
7icanon
8iexten
9echo
10echoe
11...
Text editor and other TUI based programs also modify line discipline setting in order to supress echoing and signal handling.
Controlling Terminal#
Each PTY has a process who’s designated as its controlling terminal. Usually that’s the foreground
process, for more informations look up the JOB CONTROL
section of your favorite shell.
This will be the process receiving signals and inputs from the kernel (if line discipline is configured
to send them)
Running a program#
Ok, we have a terminal emulator and its shell. Let’s do something useful with them by running the yes (1) program.
Keypresses#
As already discussed, pressing the Y E S keys triggers new events, picked up by Xorg and transmitted to the terminal emulator. The emulator calls write on the PTY master side and the shell read on the slave to get the character.
Readline#
What about backspace and other editing commands like CTRL + A to jump at the beginning of the line? Those are provided by the GNU Readline library that ships with the bash shell (and many others)
echoing#
bash won’t need to write out “yes” to the PTY slave, as the line discipline’e echo takes care of it

What should the shell run?#
We are almost there, as soon as we press ENTER bash (our shell) has to find out what yes
actually is.
There are several possibilities, which bash checks one after the other:
- alias: a shortcut for another command
- built-in command: those are part of / implemented by the shell itself, like
cd
,pwd
, etc - function: at least in bash, you can define your own function
- executable: a file with execute permission that can be run by the system, like
yes
- keyword: stuff like
while
,for
, etc implemented by the shell
Let’s see an example of each. The type
builtin (line 5) tells us how each of the provided arguments
(type
, f
, yes
, ll
and while
) should be interpreted by bash.
We start by defining a function (f
, line 1) and an alias (ll
, line 4) ourself
1$ function f() { echo "you called f"; }
2$ f
3you called f
4$ alias ll="ls -al"
5$ type type f yes ll while
6type is a shell builtin
7f is a function
8f ()
9{
10 echo "you called f"
11}
12yes is /usr/bin/yes
13ll is aliased to 'ls -al'
14while is a shell keyword
$PATH#
So, yes
is neither a builtin or a function, but how does bash knows that it is an executable?
The answer lies in the $PATH
environment variable, which contains all the… paths… to check for
executables, separated by a :
$ echo $PATH
/home/fbtd/.local/bin/:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home/fbtd/scripts
yes
is located in the /usr/bin
folder
Fork / Exec#
Now our shell knows what to execure: it
forks and execs
the new process /usr/bin/yes
, which inherits the PTY slave’s
file descriptors and promptly floods it with y
stop it!#
The new process took over our terminal! Nothing a CTRL+C can’t fix.
SIGINT
signal and exits, giving back control to the shell:

That’s it! I hope you enjoyed this short journey in the world of terminals, see you next time!
Further readings#
The TTY demystified, by Linus Åkesson
TTY subsystem, Linux Kernel doc
***
Comments, suggestions or ideas? Let me know here