备注
AI Translation Notice
This document was automatically translated by hunyuan-turbos-latest model, for reference only.
Source document: kernel/device/tty.md
Translation time: 2025-10-09 07:02:59
Translation model:
hunyuan-turbos-latest
Please report issues via Community Channel
Linux tty Devices
dev/tty is a very special device file in Linux and other Unix-like systems. Essentially, it is an alias or shortcut pointing to the controlling terminal of the current process.
1. Core Concept: Terminal
In the early days of computing, users interacted with computers through physical devices called “terminals.” A typical physical terminal consisted of a keyboard for input and a screen (or printer) for output.
In modern Linux systems, physical terminals are uncommon, having been replaced by terminal emulators (Terminal Emulator) such as GNOME Terminal, Konsole, xterm, iTerm2, etc. These are software programs under graphical interfaces that simulate the behavior of physical terminals.
Additionally, there are consoles (Console), which are terminals directly connected to computer hardware, typically used when there is no graphical interface or when the graphical interface has crashed. In Linux, you can switch to a virtual console using Ctrl + Alt + F1-F6.
Regardless of the form, the system manages these terminal sessions through a driver subsystem called TTY, named after the early “Teletypewriter.”
2. TTY Device Files
In Linux, “everything is a file.” The system communicates with hardware devices through special files in the /dev directory. For terminals, there is also a series of device files, typically located in the /dev/ directory, such as:
/dev/ttyS0, /dev/ttyS1, …: Physical serial port devices.
/dev/tty1, /dev/tty2, …: Virtual consoles.
/dev/pts/0, /dev/pts/1, …: Pseudo-terminals. These are the most commonly used; when you open a terminal emulator window, the system creates a pseudo-terminal and assigns it a device file like
/dev/pts/0.
Each terminal session (e.g., a terminal window you open) is associated with a specific TTY device file. You can use the tty command to view the device file corresponding to the current terminal:
Bash
$ tty
/dev/pts/0
3. The Role of /dev/tty: A Dynamic Link to the “Current” Terminal
Now let’s return to the main subject, /dev/tty.
Imagine you are writing a program that needs to interact directly with the user (read the user’s input or display information on the user’s screen), regardless of where the program ultimately runs.
If the user runs your program on virtual console
tty2, the program should read from and write to/dev/tty2.If the user runs it in a GNOME Terminal window, the program may need to read from and write to
/dev/pts/5.If the user logs in remotely via
ssh, the program needs to read from and write to another pseudo-terminal device.
Having the program determine which terminal it is running on would be very complex and unreliable.
/dev/tty exists to solve this problem. No matter what the specific device (e.g., /dev/tty2 or /dev/pts/5) of a process’s “controlling terminal” is, /dev/tty is always a link pointing to this controlling terminal.
When a program opens the /dev/tty file, the kernel automatically redirects this file descriptor to the current process’s actual controlling terminal. Thus, program developers do not need to worry about the underlying specific TTY device; they only need to uniformly read from and write to /dev/tty, ensuring interaction with the current user.
In short, /dev/tty tells the program: “Send the information to the user who started you, no matter where they are.”
4. The Difference Between /dev/tty and Standard Input/Output/Error (stdin, stdout, stderr)
You might ask: This sounds similar to standard input (stdin) and standard output (stdout). What’s the difference?
In most cases, a process’s standard input, output, and error streams are by default connected to its controlling terminal. For example, when you run the ls command, its stdout is your terminal by default, so you can see the file list on the screen.
However, redirection (Redirection) changes this default behavior.
ls > files.txt: Thestdoutof thelscommand is redirected to thefiles.txtfile instead of the terminal.cat my_script.sh | bash: Thestdinof thebashprocess is redirected to a pipe (|), reading content from the output of thecatcommand instead of the keyboard.
In such cases, if the program still wants to forcibly interact with the user (for example, a script that requires the user to input a password), it can no longer rely on stdin or stdout, because they may have been redirected to a file or pipe, no longer the user’s screen and keyboard.
This is where /dev/tty comes into play.
Reading from and writing to /dev/tty bypasses the redirection of standard input/output and directly accesses the controlling terminal.
Example:
Let’s look at a practical example. Suppose we have a script ask_password.sh:
Bash
#!/bin/bash
# 尝试从标准输入读取密码
echo "Enter password (from stdin):"
read password_stdin
# 现在,强制从控制终端读取密码
echo "Enter password again (from /dev/tty):"
read password_tty < /dev/tty
echo "Password from stdin: $password_stdin"
echo "Password from tty: $password_tty"
Now, we run it normally:
Bash
$ ./ask_password.sh
Enter password (from stdin):
my_secret_pass
Enter password again (from /dev/tty):
my_secret_pass
Password from stdin: my_secret_pass
Password from tty: my_secret_pass
There seems to be no difference. But now, let’s try running it with redirection:
Bash
$ echo "password_from_file" | ./ask_password.sh
Enter password (from stdin):
Enter password again (from /dev/tty):
my_real_secret <-- 这里光标会停住,等待你从键盘输入
Password from stdin: password_from_file
Password from tty: my_real_secret
Analysis:
The first
readcommand reads fromstdin. Since we redirected the output ofechoto the script’sstdinvia a pipe, it reads “password_from_file”.The second
readcommand is explicitly redirected to read from/dev/tty(< /dev/tty). This operation bypasses thestdinpipe and directly accesses your keyboard and screen. Therefore, it will stop and wait for you to manually input the password.
This is the core value of /dev/tty: providing a reliable channel to interact with the user’s terminal, regardless of how standard streams are redirected. Programs like ssh and sudo that require secure password input use this mechanism internally.
Summary
Feature |
Description |
|---|---|
Definition |
A special device file that serves as an alias or shortcut for the current process’s controlling terminal. |
Function |
Provides a stable and reliable way for programs to interact with the user’s terminal that launched them. |
Dynamism |
It is not a specific device itself but a dynamically managed link by the kernel, pointing to the specific TTY device. |
Difference from stdin/stdout |
When standard input/output/error streams are redirected to files, pipes, or other processes, |
Typical Uses |
- Programs requiring user input of passwords or confirmations (e.g., |
How is /dev/tty Typically Used in User Programs?
Well, the core goal of using /dev/tty in user programs is: to bypass potentially redirected standard input/output streams and forcibly interact directly with the user’s controlling terminal.
This is very common in the following scenarios:
Requesting Sensitive Information: Such as passwords, private key passwords, etc. Even if a script’s output is redirected to a log file, you do not want the password prompt and input process to be recorded.
Interactive Confirmation: In a script that may be automatically called, before performing dangerous operations (e.g.,
rm -rf /), it is necessary to force the user to manually confirm.Diagnostics and Debugging: Printing debugging information to the user’s screen, even if the user has redirected the script’s standard output elsewhere.
Fullscreen or Cursor-Based Applications: Programs like
vimandtopneed to directly control the terminal’s screen, colors, and cursor positions, and they interact directly with the TTY device.
Summary
The pattern of using /dev/tty in user programs is very consistent:
Open the File: Open
/dev/ttylike a regular file, typically requiring read/write permissions (r+).Error Handling: Check if the open operation was successful. If a process does not have a controlling terminal (e.g., a background daemon process started by
systemd), opening/dev/ttywill fail. The program needs to handle this situation properly.Writing (Output): Use standard file writing functions (e.g.,
fprintf,write) to write data to the opened/dev/ttyfile descriptor, which will display prompt information on the user’s screen.Reading (Input): Use standard file reading functions (e.g.,
fgets,read) to read data from/dev/tty, obtaining the user’s keyboard input.Close the File: After completing the interaction, close the file descriptor.
What is the Role of /dev/ptmx and Files Under /dev/pts/?
Well, let’s delve into the files under /dev/ptmx and /dev/pts/. These two components are the core of the pseudo-terminal (PTY) mechanism in modern Linux systems, crucial for terminal emulators and SSH remote login functions we use daily.
Simply put, they together create a “fake” terminal device, making programs (e.g., bash) think they are communicating with a physical terminal, when in fact they are interacting with software (e.g., GNOME Terminal or sshd).
This mechanism consists of two parts:
Master Device: Represented by
/dev/ptmx.Slave Device: Located in the
/dev/pts/directory, such as/dev/pts/0,/dev/pts/1, etc.
Let’s delve deeper into their respective roles and how they work together.
1. The Concept of Pseudo-Terminals (PTY)
First, understand why pseudo-terminals are needed. In early Unix systems, users interacted with computers through physical serial ports (e.g., /dev/ttyS0) connected to physical terminals. Later, with the development of graphical interfaces and networks, we needed a way to simulate this hardware terminal at the software level.
Pseudo-terminals are this software simulation of terminals. They act like a pipe, with a “device” at each end:
Master Side: Held and controlled by terminal emulators (e.g., xterm, GNOME Terminal) or remote login services (e.g.,
sshd).Slave Side: Provided for applications (e.g.,
shell,vim,top) to use. This slave device looks and behaves exactly like a true physical terminal device.
When you type on the keyboard in a terminal emulator, the terminal emulator program writes data from the master side; the kernel forwards this data to the slave side, and the shell program can read your input from the slave side. Conversely, when the shell program produces output (e.g., the result of ls), it writes data to the slave side; the kernel forwards it to the master side, and the terminal emulator reads this data and displays it in the window.
2. /dev/ptmx: The Pseudo-Terminal Master Multiplexer
/dev/ptmx is a special character device file, whose name is an abbreviation for “pseudo-terminal multiplexer.” You can think of it as a factory for creating pseudo-terminal master/slave device pairs.
Its core functions are:
Creating New PTY Pairs: When a program (e.g., a terminal emulator) needs a new pseudo-terminal, it opens the
/dev/ptmxfile.Returning the Master Device File Descriptor: This
openoperation successfully returns a file descriptor. This file descriptor represents the master side of the newly created PTY pair.Dynamically Creating the Slave Device: While opening
/dev/ptmx, the kernel dynamically creates a corresponding slave device (Slave) file in the/dev/pts/directory, such as/dev/pts/0.Providing a Control Interface: The program can perform
ioctl()system calls on the file descriptor returned by/dev/ptmxto configure the PTY, such as obtaining the name of the slave device, unlocking the slave device, etc.
Key Point: You cannot directly perform extensive read/write operations on /dev/ptmx. Its primary purpose is to request and create a new PTY pair through open() calls. Subsequent read/write operations are performed through the file descriptor returned by open(). Each opening of /dev/ptmx creates a brand new, independent PTY master/slave device pair.
3. The /dev/pts/ Directory and Its Files
/dev/pts is a special file system of type devpts. This directory is specifically used to store pseudo-terminal slave device files.
Dynamically Created: The files in this directory (e.g.,
/dev/pts/0,/dev/pts/1, …) are not permanently present. They are dynamically created by the kernel when the corresponding PTY master device is created (i.e., when/dev/ptmxis opened).Role of Slave Devices: Each
/dev/pts/Nfile acts as the slave side of a PTY pair. It is a standard TTY device, and applications (e.g.,bash) can open it, read user input, and write program output just like any other terminal device.Assigned to Shell: After creating a PTY pair, the terminal emulator will
forka child process and redirect the standard input, output, and error of the child process to this newly created slave device (e.g.,/dev/pts/0), then executebashor another shell. Thus,bash“owns” this pseudo-terminal as its controlling terminal.
You can use the tty command to view the slave device associated with the current shell:
Bash
$ tty
/dev/pts/0
If you open a new terminal window and run tty in the new window, you might see:
Bash
$ tty
/dev/pts/1
4. Complete Creation Process
Let’s string the entire process together to see what happens in the background when you open a new terminal window:
Open ptmx: The GNOME Terminal program calls
open("/dev/ptmx", O_RDWR).Create PTY Pair: The kernel receives the request and creates a new pseudo-terminal master/slave device pair.
Return Master Device FD: The
opencall returns a file descriptor (e.g.,fd=3) to GNOME Terminal. Thisfdis the master side of the PTY.Create Slave Device File: Simultaneously, the kernel creates a new slave device file in the
/dev/pts/directory, such as/dev/pts/5.Unlock and Authorize: The GNOME Terminal program performs a series of
ioctlcalls (e.g.,grantptandunlockpt) on the file descriptor of the master device to set the permissions and status of the slave device/dev/pts/5, making it usable.Get Slave Device Name: The GNOME Terminal queries the name of the slave device associated with the master device of
fd=3through anioctlcall toptsname, obtaining the string “/dev/pts/5”.Create Child Process: The GNOME Terminal calls
fork()to create a child process.Set Session and Redirect: In the child process:
A new session is created (
setsid()), and/dev/pts/5is set as the controlling terminal of the session.The standard input, output, and error (file descriptors 0, 1, 2) are closed.
/dev/pts/5is opened and duplicated to file descriptors 0, 1, 2. Now, the child process’sstdin,stdout, andstderrall point to this pseudo-terminal slave device.
Execute Shell: The child process calls
execve("/bin/bash", ...)to startbash.bashinherits the already set file descriptors, naturally reading commands from/dev/pts/5and writing results to it.Data Forwarding:
You type
lsin the GNOME Terminal window.The GNOME Terminal program reads the input from keyboard events and writes “ls\n” through the file descriptor of the master device
fd=3.The kernel forwards the data from the master side to the slave side
/dev/pts/5.bashreads “ls\n” from its standard input (/dev/pts/5) and executes the command.The output of
lsis written bybashto its standard output (/dev/pts/5).The kernel forwards the data from the slave side to the master side.
The GNOME Terminal program reads the output result of
lsthrough the file descriptor of the master devicefd=3and renders it in the window.
Summary
Component |
Role |
Function |
|---|---|---|
|
Master Device Factory |
As a single entry point, when opened, it creates a new pseudo-terminal master/slave device pair and returns the file descriptor representing the master device. |
Files Under |
Slave Devices |
These are dynamically created device files, each representing the slave device of a pseudo-terminal. They provide a standard TTY interface for applications (e.g., shells) to interact as if with a physical terminal. |
A Simple User Program Demonstrating the Use of /dev/ptmx
Well, here is a simple C language user program that clearly demonstrates how to use /dev/ptmx to create a pseudo-terminal and start a shell (/bin/bash) within this pseudo-terminal.
This program will act as the most basic “terminal emulator.” It handles the master side, while the bash shell runs on the slave side it creates.
Program Function Breakdown
Open
/dev/ptmx: Obtain a file descriptor for the pseudo-terminal master device.Initialize the Slave Device: Call
grantpt()andunlockpt()to set the permissions and status of the slave device (slave PTY).Get the Slave Device Name: Call
ptsname()to obtain the path of the corresponding slave device (e.g.,/dev/pts/3).Create a Child Process: Use
fork()to create a child process, which will run the shell.Child Process Setup: In the child process, associate its session with the slave device, redirect standard input, output, and error to the slave device, and then execute
/bin/bash.Parent Process Communication: In the parent process, listen to input from the user’s current terminal and output from
bash, transferring data back and forth between them to achieve interaction.
Source Code (ptmx_demo.c)
C
#define _XOPEN_SOURCE 600 // Needed for grantpt, unlockpt, ptsname
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <termios.h> // Not strictly needed for the demo, but good practice
#include <string.h>
int main() {
int master_fd;
char *slave_name;
pid_t pid;
// 1. 打开 /dev/ptmx 来获取一个主设备文件描述符
master_fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (master_fd < 0) {
perror("Error opening /dev/ptmx");
return 1;
}
printf("1. Master PTY opened with fd: %d\n", master_fd);
// 2. 授权并解锁从设备
if (grantpt(master_fd) != 0) {
perror("Error calling grantpt");
close(master_fd);
return 1;
}
if (unlockpt(master_fd) != 0) {
perror("Error calling unlockpt");
close(master_fd);
return 1;
}
printf("2. Slave PTY permissions granted and unlocked.\n");
// 3. 获取从设备的名字
slave_name = ptsname(master_fd);
if (slave_name == NULL) {
perror("Error calling ptsname");
close(master_fd);
return 1;
}
printf("3. Slave PTY name is: %s\n", slave_name);
// 4. 创建子进程
pid = fork();
if (pid < 0) {
perror("Error calling fork");
close(master_fd);
return 1;
}
// 5. 子进程的代码
if (pid == 0) {
int slave_fd;
// 创建一个新的会话,使子进程成为会话领导者
// 这是让从设备成为控制终端的关键步骤
if (setsid() < 0) {
perror("setsid failed");
exit(1);
}
// 打开从设备
slave_fd = open(slave_name, O_RDWR);
if (slave_fd < 0) {
perror("Error opening slave pty");
exit(1);
}
// 将从设备设置为该进程的控制终端
// TIOCSCTTY 是 "Set Controlling TTY" 的意思
if (ioctl(slave_fd, TIOCSCTTY, NULL) < 0) {
perror("ioctl TIOCSCTTY failed");
exit(1);
}
// 将子进程的标准输入、输出、错误重定向到从设备
dup2(slave_fd, STDIN_FILENO); // fd 0
dup2(slave_fd, STDOUT_FILENO); // fd 1
dup2(slave_fd, STDERR_FILENO); // fd 2
// 关闭不再需要的文件描述符
close(master_fd); // 子进程不需要主设备
close(slave_fd); // 因为已经 dup2 了,这个原始的也可以关了
// 执行一个新的 bash shell
printf("--- Starting Bash Shell in Slave PTY ---\n\n");
fflush(stdout);
execlp("/bin/bash", "bash", NULL);
// 如果 execlp 成功,下面的代码不会被执行
perror("execlp failed");
exit(1);
}
// 6. 父进程的代码
printf("4. Forked child process with PID: %d\n", pid);
printf("5. Parent process will now forward data between stdin and master PTY.\n");
printf("--- You are now interacting with the new shell. Type 'exit' to quit. ---\n\n");
// 父进程不需要从设备
// close(slave_fd) in parent - it was never opened here
char buffer[256];
ssize_t nread;
// 循环,直到子进程退出
while (1) {
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(STDIN_FILENO, &read_fds); // 监听当前终端的输入
FD_SET(master_fd, &read_fds); // 监听主设备的输出 (来自子进程shell)
// 使用 select 阻塞,直到有数据可读
if (select(master_fd + 1, &read_fds, NULL, NULL, NULL) < 0) {
perror("select failed");
break;
}
// 检查是否是当前终端有输入
if (FD_ISSET(STDIN_FILENO, &read_fds)) {
nread = read(STDIN_FILENO, buffer, sizeof(buffer));
if (nread > 0) {
// 将用户的输入写入主设备,数据会流向子进程的shell
write(master_fd, buffer, nread);
} else {
break; // 读错误或EOF
}
}
// 检查是否是主设备有输出
if (FD_ISSET(master_fd, &read_fds)) {
nread = read(master_fd, buffer, sizeof(buffer));
if (nread > 0) {
// 将来自shell的输出写入当前终端的屏幕
write(STDOUT_FILENO, buffer, nread);
} else {
// 读取到 0 或 -1,意味着子进程的另一端关闭了连接
// 通常是 shell 执行了 exit
break;
}
}
}
printf("\n--- Shell terminated. Parent process is shutting down. ---\n");
close(master_fd);
wait(NULL); // 等待子进程完全终止
return 0;
}
How to Compile and Run
Save the Code: Save the above code as
ptmx_demo.c.Compile: Compile using gcc.
Bash
gcc -o ptmx_demo ptmx_demo.c _en```_translated_label__
Run: Execute the generated executable file.
Bash
_en
_translated_label__ ./ptmx_demo _en
Runtime Output and Interaction
When you run the program, you will see output similar to the following:
1. Master PTY opened with fd: 3
2. Slave PTY permissions granted and unlocked.
3. Slave PTY name is: /dev/pts/2
4. Forked child process with PID: 12345
5. Parent process will now forward data between stdin and master PTY.
--- You are now interacting with the new shell. Type 'exit' to quit. ---
bash-5.1$
What Happened?
Your
./ptmx_demoprogram created a new pseudo-terminal/dev/pts/2.It started a new
bashprocess, whose “world” is/dev/pts/2.Your
./ptmx_demoprogram acts as an intermediary:It reads keyboard input from your current terminal (e.g.,
/dev/pts/1).Writes your input (e.g.,
ls -l) to the master device (master_fd).The kernel forwards the data to the slave device
/dev/pts/2.bashreadsls -lfrom its standard input (/dev/pts/2) and executes it.ls’s output is written bybashto its standard output (/dev/pts/2).The kernel forwards the data to the master device.
Your
./ptmx_demoprogram reads the result from the master device (master_fd) and prints it to its standard output, so you see the result ofls -lon the screen.
You can execute commands as in a normal shell:
bash-5.1$ pwd
/home/user/test
bash-5.1$ whoami
user
bash-5.1$ ps -f
UID PID PPID C STIME TTY TIME CMD
user 12344 5678 0 10:30 pts/1 00:00:00 ./ptmx_demo
user 12345 12344 0 10:30 pts/2 00:00:00 bash
user 12350 12345 0 10:31 pts/2 00:00:00 ps -f
Note the output of the ps command! Our ptmx_demo program runs on pts/1 (your original terminal), while the bash process it creates runs in a completely new terminal pts/2.
When you type exit and press Enter, the bash process terminates, causing the pseudo-terminal connection to close. The read(master_fd, ...) in the parent process returns 0, the loop breaks, and the program exits gracefully.
This example fully demonstrates the core use of /dev/ptmx: creating an isolated terminal session environment and allowing a program (parent process) to fully control the input and output of another program (child process). This is also the foundational working principle of all terminal emulators, ssh services, and tmux/screen tools.