详解控制台与终端

有趣的问题:Linux 的终端,控制台,TTY, PTY 究竟是什么?它们与进程有什么关系?

历史回顾:控制台

  • 控制台是一个直接控制设备的面板(属于设备的一部分)
  • 计算机设备的控制台:按键 & 指示灯 (键盘 & 显示器)
  • 早期的电子计算机必然有一个控制台

image.png

历史回顾:终端 (Terminal)

  • 终端是一台独立于计算机的机器,是能够用来和计算机进行交互的设备

image.png

TTY -- 即:TeleType Writer 电传打字机,一种终端设备

历史发展进程。。。

  • 电传打字机已经淘汰
  • 计算机上的输入设备和显示设备从主机独立出来
  • 控制台与终端的物理表现形式逐渐趋近
  • 计算机开始支持多任务处理
  • 。。。

image.png

终端与进程

  • TTY 演变为 Linux 中的抽象概念,对于进程而言,TTY是一种输入输出设备

image.png

虚拟终端与伪终端

各种终端类型

类型说明
虚拟终端(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(从), ...

内核终端模拟器

image.png

伪终端模型

image.png

伪终端(gnome-terminal)

image.png

伪终端
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

伪终端程序设计原理

image.png

站在 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

终端必然与进程关联才有意义!那么,进程之间除了父子关系,是否还有其它关系?

TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧