Linux 终端 I/O 与行为控制全解析
1. 终端基础概念
在 Linux 系统中,终端是我们与系统交互的重要工具,它有多种形式,如 X 窗口系统中的终端窗口、通过 Ctrl + Alt + F1 至 F7 访问的七个终端、老式串行终端、拨号终端以及远程终端(如 Secure Shell,即 SSH)。终端主要分为 TTY(TeleTYpewriter 的缩写)和 PTY(Pseudo - TeletYpewriter 的缩写)两类。
- TTY :是硬件终端,例如通过 Ctrl + Alt + F1 至 F7 访问的控制台或串行控制台。
- PTY :是伪终端,通过软件模拟实现。像 xterm、rxvt、Konsole、Gnome Terminal 等程序,或者如 tmux 这样的终端复用器都属于 PTY,远程终端如 SSH 也可看作 PTY。
了解终端的相关信息并对其进行控制,有助于我们编写更优质的软件,例如在密码提示时隐藏输入内容。
1.1 技术要求
在学习过程中,我们需要一些常用工具,如 GNU 编译器集合(GCC)、Make 工具和通用的 Makefile,此外还需要一个名为 screen 的程序。如果系统中尚未安装该程序,可以使用系统的包管理器进行安装:
- 对于 Debian/Ubuntu 系统: sudo apt - get install screen
- 对于 CentOS/Fedora 系统: sudo dnf install screen
所有相关代码示例可从 此处 下载,也可查看 代码演示视频 。
2. 查看终端信息
2.1 准备工作
此操作无需特殊要求,仅使用系统中已安装的标准程序即可。
2.2 操作步骤
- 确定当前使用的 TTY/PTY :在终端中输入
tty命令,系统会显示当前使用的 TTY 或 PTY。例如:
$> tty
/dev/pts/24
- 检查对应文件类型 :使用
ls -l和file命令检查该终端对应的文件类型,它属于字符特殊文件。示例如下:
$> ls -l /dev/pts/24
crw--w---- 1 jake tty 136, 24 jan 3 23:19 /dev/pts/24
$> file /dev/pts/24
/dev/pts/24: character special (136/24)
- 查看终端属性 :使用
stty -a命令查看终端的所有属性,包括终端的大小(行数和列数)、速度(在串行终端、拨号等场景中重要)以及各种 Ctrl 键组合对应的功能等。示例如下:
$> stty -a
speed 38400 baud; rows 14; columns 88; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U;
eof = ^D; eol = M - ^?; eol2 = M - ^?;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
discard = ^O; min = 1; time = 0;
- parenb - parodd - cmspar cs8 hupcl - cstopb cread - clocal - crtscts
- ignbrk brkint - ignpar - parmrk - inpck - istrip - inlcr - igncr icrnl ixon - ixoff - iuclc
ixany imaxbel iutf8
opost - olcuc - ocrnl onlcr - onocr - onlret - ofill - ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok - echonl - noflsh - xcase - tostop - echoprt echoctl
echoke - flusho - extproc
- 查看其他终端属性 :若要查看其他终端的属性,需确保该终端的所有者是当前登录用户,否则会出现权限拒绝错误。示例如下:
$> stty -F /dev/pts/33
speed 38400 baud; line = 0;
lnext = <undef>; discard = <undef>; min = 1;
time = 0; - brkint - icrnl ixoff - imaxbel iutf8
- icanon - echo
$> stty -F /dev/tty2
stty: /dev/tty2: Permission denied
2.3 工作原理
在 Linux 系统中,可能有大量用户通过 TTY 或 PTY 登录。过去,多使用硬件终端(TTY)通过串行线路连接到计算机,如今硬件终端已较为少见,更多是通过 SSH 登录或使用终端程序。
以 /dev/pts/24 为例,PTY 由主设备(PTM)和从设备(PTS)两部分组成,我们连接的是从设备(PTS)。主设备负责打开或创建伪终端,而我们实际使用的是从设备。
在 stty -a 输出中,以减号开头的选项表示禁用,如 -parenb ;无减号的选项表示启用,如 cs8 。 -parenb 用于生成奇偶校验位并期望输入中也有相应校验位,常用于拨号连接和串行通信; cs8 则将字符大小设置为 8 位。 stty 程序可用于查看和设置终端属性。
2.4 相关参考
可通过 man 1 tty 和 man 1 stty 命令获取更多相关信息。
3. 使用 stty 更改终端设置
3.1 准备工作
此操作无需特殊要求。
3.2 操作步骤
- 关闭终端回显 :在终端中输入
stty -echo命令关闭回显,此时输入的内容将不会显示,但命令仍可正常执行。示例如下:
$> stty -echo
$> whoami jake
$>
- 重新开启终端回显 :输入
stty echo命令重新开启回显。注意,输入该命令时不会显示输入内容。
$> stty echo
$> whoami
jake
- 更改特殊键序列 :可以将 EOF 字符从默认的 Ctrl + D 重新绑定为单个点(.)。
$> stty eof .
输入单个点(.)后,当前终端将退出或注销。重新启动终端或重新登录后,设置将恢复为默认值。
- 保存终端设置 :先进行必要的设置更改,如将 EOF 设置为点,然后使用 stty --save 命令保存设置,该命令会输出一串十六进制数字,可将其重定向到文件中。
$> stty eof .
$> stty --save
5500:5:bf:8a3b:3:1c:7f:15:2e:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
$> stty --save > my - tty - settings
- 恢复终端设置 :注销后重新登录,输入单个点不会有任何反应。使用以下命令从保存的文件中恢复设置:
$> stty $(cat my - tty - settings)
再次输入单个点,终端将退出或注销。
3.3 工作原理
终端通常是“哑”设备,需要大量设置才能正常工作,这是早期硬件电传打字机遗留下来的特性。 stty 程序用于设置终端设备的属性,减号开头的选项表示禁用,无减号的选项表示启用。目前,保存 TTY 设置的有效方法是将设置保存到文件中,以便后续恢复。
4. 调查 TTY 和 PTY 并进行消息发送
4.1 操作步骤
- 准备工作 :打开三到四个终端窗口,如果未使用 X 窗口系统,可在多个 TTY 上登录;若使用远程服务器,可多次登录。
- 查看登录用户信息 :在其中一个终端中输入
who命令,可查看所有登录用户的信息,包括使用的 TTY/PTY 以及登录时间。示例如下:
$> who
root tty1 Jan 5 16:03
jake pts/0 Jan 5 16:04 (192.168.0.34)
jake pts/1 Jan 5 16:04 (192.168.0.34)
jake pts/2 Jan 5 16:04 (192.168.0.34)
- 查看用户当前使用的程序 :使用
w命令可查看每个终端上用户当前使用的程序。
$> w
16:09:33 up 7 min, 4 users, load average: 0.00, 0.16, 0.13
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root tty1 - 16:03 6:05 0.07s 0.07s -bash
jake pts/0 192.168.0.34 16:04 5:25 0.01s 0.01s -bash
jake pts/1 192.168.0.34 16:04 0.00s 0.04s 0.01s w
jake pts/2 192.168.0.34 16:04 5:02 0.02s 0.02s -bash
- 确定当前使用的终端 :输入
tty命令确定当前使用的终端。
$> tty
/dev/pts/1
- 向其他终端发送消息 :由于终端在文件系统中以文件形式表示,可使用重定向向其他终端发送消息。例如,向
/dev/pts/2终端发送消息:
$> echo "Hello" > /dev/pts/2
但如果向其他用户(如 root)登录的终端发送消息,可能会因权限问题失败。
$> echo "Hello" > /dev/tty1
- bash: /dev/tty1: Permission denied
- 使用
write程序发送消息 :使用mesg程序允许或禁止接收消息。以 root 用户为例,允许接收消息:
#> tty
/dev/tty1
#> whoami
root
#> mesg y
其他用户可使用 write 程序向该终端发送消息:
$> write root /dev/tty1
Hello! How are you doing?
Ctrl + D
- 向所有终端发送消息 :root 用户可使用
wall命令向所有登录用户发送消息,例如通知即将重启:
#> wall "The machine will be rebooted later tonight"
3.3 工作原理
因为终端在文件系统中以文件形式存在,所以可以像操作文件一样向其发送消息,但需要遵循文件的权限规则。 write 程序允许用户在对方允许的情况下相互发送消息, wall 命令用于向所有终端发送消息,root 用户可向禁止接收消息的用户发送消息。
3.4 相关参考
可通过 man 1 write 、 man 1 wall 和 man 1 mesg 命令获取更多相关信息。
5. 检查是否为 TTY
5.1 准备工作
需要 GCC 编译器、Make 工具和通用的 Makefile,通用 Makefile 可从 此处 下载。
5.2 操作步骤
- 编写程序 :创建一个名为
ttyinfo.c的文件,编写以下代码:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main(void)
{
if ( (isatty(STDOUT_FILENO) == 1) )
{
printf("It's a TTY with the name %s\n",
ttyname(STDOUT_FILENO));
}
else
{
perror("isatty");
}
printf("Hello world\n");
return 0;
}
- 编译程序 :在终端中输入
make ttyinfo命令编译程序。
$> make ttyinfo
gcc -Wall -Wextra -pedantic - std=c99 ttyinfo.c -o ttyinfo
- 运行程序 :
- 不进行重定向运行程序,会输出终端名称和 “Hello world”:
$> ./ttyinfo
It's a TTY with the name /dev/pts/10
Hello world
- 将文件描述符 1 重定向到文件,程序会输出错误信息,但 “Hello world” 会被重定向到文件中:
$> ./ttyinfo > my - file
isatty: Inappropriate ioctl for device
$> cat my - file
Hello world
- 将文件描述符 1 重定向到 `/dev/stdout`,程序正常运行:
$> ./ttyinfo > /dev/stdout
It's a TTY with the name /dev/pts/10
Hello world
- 将文件描述符 1 重定向到自己的终端设备,程序正常运行:
$> tty
/dev/pts/10
$> ./ttyinfo > /dev/pts/10
It's a TTY with the name /dev/pts/10
Hello world
- 打开第二个终端,获取其 TTY 名称(如 `/dev/pts/26`),将程序输出重定向到该终端:
$> ./ttyinfo > /dev/pts/26
当前终端无输出,第二个终端会显示程序输出。
5.3 工作原理
STDOUT_FILENO 宏代表整数 1,即文件描述符 1。当使用 > 重定向标准输出时,实际上是重定向文件描述符 1。如果将文件描述符 1 重定向到文件,由于文件不是终端, isatty() 函数会返回错误信息。当将文件描述符 1 重定向回 /dev/stdout 或其他终端时,程序会正常运行。
5.4 相关参考
可通过 man 3 isatty 和 man 3 ttyname 命令获取更多相关信息。
6. 创建 PTY
6.1 操作步骤
使用 C 程序创建 PTY,PTY 由主设备(PTM)和从设备(PTS)两部分组成。程序将创建 PTY 并在当前终端打印从设备的路径,之后可使用 screen 程序连接到该 PTS 进行操作。
6.2 代码示例(待补充完整代码)
// 此处应补充创建 PTY 的代码
6.3 工作原理
程序通过特定的系统调用创建 PTY 的主设备和从设备,主设备负责管理和控制,从设备供用户使用。创建完成后,可通过 screen 程序连接到从设备进行交互。
6.4 后续操作
连接到 PTS 后,可在其中输入命令,实现与终端的交互。
通过以上内容,我们全面了解了 Linux 终端的相关知识,包括终端的类型、查看和更改终端设置、向其他终端发送消息以及创建 PTY 等操作,这些知识将帮助我们更好地与 Linux 系统进行交互。
6. 创建 PTY(续)
6.2 代码示例
以下是一个完整的 C 程序示例,用于创建 PTY 并打印从设备的路径:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
int main() {
int master_fd;
char *slave_name;
// 创建 PTY
master_fd = posix_openpt(O_RDWR | O_NOCTTY);
if (master_fd == -1) {
perror("posix_openpt");
return 1;
}
// 解锁 PTY
if (grantpt(master_fd) == -1) {
perror("grantpt");
close(master_fd);
return 1;
}
// 使 PTY 从设备可用
if (unlockpt(master_fd) == -1) {
perror("unlockpt");
close(master_fd);
return 1;
}
// 获取从设备的名称
slave_name = ptsname(master_fd);
if (slave_name == NULL) {
perror("ptsname");
close(master_fd);
return 1;
}
// 打印从设备的路径
printf("PTY slave device: %s\n", slave_name);
// 可以在这里添加代码,使用 screen 连接到从设备
// 例如:system("screen <slave_name>");
close(master_fd);
return 0;
}
6.3 工作原理
-
posix_openpt函数用于打开一个新的伪终端主设备,并返回其文件描述符。 -
grantpt函数用于授予对伪终端从设备的访问权限。 -
unlockpt函数用于解锁伪终端从设备,使其可以被使用。 -
ptsname函数用于获取伪终端从设备的名称。
通过这些步骤,程序成功创建了一个 PTY,并获取了从设备的路径。
6.4 后续操作
连接到 PTS 后,可在其中输入命令,实现与终端的交互。例如,使用 screen 程序连接到从设备:
$ screen /dev/pts/X # X 为实际的从设备编号
连接成功后,你可以在 screen 会话中输入命令,就像在普通终端中一样。
7. 终端操作总结
为了更好地理解和掌握前面介绍的终端操作,下面通过一个表格进行总结:
| 操作类型 | 命令/程序 | 功能描述 | 示例 |
| ---- | ---- | ---- | ---- |
| 查看终端信息 | tty | 显示当前使用的 TTY 或 PTY | $ tty |
| | ls -l 、 file | 检查终端对应文件的类型 | $ ls -l /dev/pts/24
$ file /dev/pts/24 |
| | stty -a | 查看终端的所有属性 | $ stty -a |
| 更改终端设置 | stty -echo | 关闭终端回显 | $ stty -echo |
| | stty echo | 开启终端回显 | $ stty echo |
| | stty eof . | 更改 EOF 字符 | $ stty eof . |
| | stty --save | 保存终端设置 | $ stty --save > my - tty - settings |
| | stty $(cat my - tty - settings) | 恢复终端设置 | $ stty $(cat my - tty - settings) |
| 调查 TTY 和 PTY 并发送消息 | who | 查看所有登录用户的信息 | $ who |
| | w | 查看每个终端上用户当前使用的程序 | $ w |
| | echo "message" > /dev/pts/X | 向指定终端发送消息 | $ echo "Hello" > /dev/pts/2 |
| | mesg y | 允许接收消息 | # mesg y |
| | write user /dev/ttyX | 向指定用户的终端发送消息 | $ write root /dev/tty1 |
| | wall "message" | 向所有登录用户发送消息 | # wall "The machine will be rebooted later tonight" |
| 检查是否为 TTY | isatty() 、 ttyname() | 检查文件描述符是否为终端,并获取终端名称 | 见 ttyinfo.c 示例 |
| 创建 PTY | C 程序 | 创建 PTY 并获取从设备路径 | 见前面的 C 程序示例 |
8. 终端操作流程图
下面是一个 mermaid 格式的流程图,展示了常见的终端操作流程:
graph TD;
A[开始] --> B{选择操作};
B --> C[查看终端信息];
B --> D[更改终端设置];
B --> E[调查 TTY 和 PTY 并发送消息];
B --> F[检查是否为 TTY];
B --> G[创建 PTY];
C --> H[tty 命令];
C --> I[ls -l 和 file 命令];
C --> J[stty -a 命令];
D --> K[stty -echo 关闭回显];
D --> L[stty echo 开启回显];
D --> M[stty eof . 更改 EOF 字符];
D --> N[stty --save 保存设置];
D --> O[stty $(cat settings) 恢复设置];
E --> P[who 命令查看用户信息];
E --> Q[w 命令查看用户程序];
E --> R[echo 重定向发送消息];
E --> S[mesg 允许接收消息];
E --> T[write 发送消息];
E --> U[wall 向所有用户发送消息];
F --> V[编写 ttyinfo.c 程序];
F --> W[编译运行程序];
G --> X[编写创建 PTY 的 C 程序];
G --> Y[编译运行程序];
H --> Z[结束];
I --> Z;
J --> Z;
K --> Z;
L --> Z;
M --> Z;
N --> Z;
O --> Z;
P --> Z;
Q --> Z;
R --> Z;
S --> Z;
T --> Z;
U --> Z;
V --> Z;
W --> Z;
X --> Z;
Y --> Z;
9. 总结与展望
通过本文的学习,我们全面了解了 Linux 终端的相关知识,包括终端的类型(TTY 和 PTY)、查看和更改终端设置、向其他终端发送消息、检查是否为 TTY 以及创建 PTY 等操作。这些知识不仅有助于我们更好地与 Linux 系统进行交互,还能帮助我们编写更高效、更安全的终端相关程序。
在未来的学习和实践中,我们可以进一步探索终端的高级功能,如自定义终端配置、开发终端应用程序等。同时,我们也可以关注终端安全问题,确保在使用终端时保护好个人信息和系统安全。
希望本文能为你在 Linux 终端编程和操作方面提供有价值的参考,让你在 Linux 系统中更加得心应手。

1603

被折叠的 条评论
为什么被折叠?



