详解控制台与终端
有趣的问题:Linux 的终端,控制台,TTY, PTY 究竟是什么?它们与进程有什么关系?
历史回顾:控制台
- 控制台是一个直接控制设备的面板(属于设备的一部分)
- 计算机设备的控制台:按键 & 指示灯 (键盘 & 显示器)
- 早期的电子计算机必然有一个控制台
历史回顾:终端 (Terminal)
- 终端是一台独立于计算机的机器,是能够用来和计算机进行交互的设备
TTY -- 即:TeleType Writer 电传打字机,一种终端设备
历史发展进程。。。
- 电传打字机已经淘汰
- 计算机上的输入设备和显示设备从主机独立出来
- 控制台与终端的物理表现形式逐渐趋近
- 计算机开始支持多任务处理
- 。。。
终端与进程
- TTY 演变为 Linux 中的抽象概念,对于进程而言,TTY是一种输入输出设备
虚拟终端与伪终端
各种终端类型
类型 | 说明 | |
虚拟终端(Virtual Terminal) | 将这一套键盘和显示器映射为 6 个终端设备 | /dev/tty1~tty6 tty0指代当前使用的终端 (CLI虚拟出) |
串口终端(Serial Port Terminal) | 将连接到窗口的外设看看作终端设备 | /dev/ttyS1,... |
终端模拟器(Terminal Emulator) | 终端模拟程序(广义) / 内核模拟模块(侠义) | Putty, MobaXTERM, 内核模块,伪终端 |
伪终端(Pseudo Terminal) | 运行在用户模式的终端模拟程序,分为主设备(pty master)和从设备(pty slave) | /dev/ptmx(主), /dev/pts/3(从), ... |
内核终端模拟器
伪终端模型
伪终端(gnome-terminal)
伪终端
tiansong@tiansong:~$ pstree -A -p -s $$
systemd(1)---systemd(968)---gnome-terminal(1885)---bash(1973)---pstree(2052)
虚拟终端
ubuntu 由 GUI 切换到 CLI : CTRL + ALT +F3
tiansong@tiansong:~$ pstree -A -p -s $$
systemd(1)---login(2064)---bash(2118)---pstree(2125)
ubuntu 由 CLI 切换到 GUI : CTRL + ALT +F1
伪终端程序设计原理
站在 shell 角度的总结:
1. shell 面对的只有 TTY 设备,而 TTY 是 Linux 中的抽象概念,因此需要一个具体的模块来支持这个抽象的概念
2. 对于虚拟终端,支持这个概念的模块就是终端模拟器(内核模块,运行于内核模式),可以直接使用键盘、显卡驱动进行输入输出
3. 对于伪终端,创建主、从设备,其中 shell 进程对接的是从设备,主设备对接用户进程 gnome-terminal (本质是 GUI 应用程序)
伪终端程序设计(master)
- 创建 PTY 主从设备:
master = posix_openpt(O_RDWR);
获取主设备权限:
grantpt(master);
// 获取设备使用权限unlockpt(master);
// 解锁设备,为读写做准备
读写主设备
c = read(master, &rx, 1);
len = write(master, txbuf, strlen(txbuf));
伪终端程序设计(slave)
- 打开 PTY 从设备,
slave = open(path_to_slave, O_RDWR);
读写设备:
write(slave, "Delphi\r", 7);
read(slave, buf, sizeof(buf) - 1);
实战伪终端程序设计
master.c
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char rx = 0;
char rxbuf[128] = {0};
char txbuf[256] = {0};
int master = 0;
int c = 0;
int i = 0;
master = posix_openpt(O_RDWR); // 连接主设备,等同于 gnome-terminal
if (master > 0) {
grantpt(master);
unlockpt(master);
printf("Slave: %s\n", ptsname(master));
while ((c = read(master, &rx, 1)) == 1) {
if (rx == '\r') {
rxbuf[i] = 0;
sprintf(txbuf, "from slave: %s\r", rxbuf); // 等同于屏幕输出
write(master, txbuf, strlen(txbuf)); // 等同于键盘输入
} else {
rxbuf[i++] = rx;
}
}
}
else {
printf("create pty error...\n");
}
return 0;
}
tiansong@tiansong:~/Desktop/linux$ gcc master.c -o master.out
tiansong@tiansong:~/Desktop/linux$ ./master.out // 不终止
Slave: /dev/pts/0
slave.c
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int slave = open(argv[1], O_RDWR); // 等同于 shell
if (slave > 0) {
char buf[256] = {0};
char *data = "D.T.Software\r";
int len = strlen(data);
write(slave, data, len);
sleep(1);
len = read(slave, buf, sizeof(buf) - 1);
buf[(len > 0) ? len : 0] = 0;
printf("Read: %s\n", buf); // system(...)
close(slave);
}
else {
}
return 0;
}
tiansong@tiansong:~/Desktop/linux$ gcc slave.c -o slave.out
tiansong@tiansong:~/Desktop/linux$ ./slave.out /dev/pts/0 // 新终端执行
Read: from slave: D.T.Software
终端必然与进程关联才有意义!那么,进程之间除了父子关系,是否还有其它关系?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。