TTY 子系统架构与源码文件解析 文章封面

TTY 子系统架构与源码文件解析

基于 Linux 4.14,iMX6ULL 配置编译。分析 drivers/tty/ 下各源码文件的职责,并梳理 TTY 子系统的分层结构。


一、TTY 子系统概述

TTY(Teletype)子系统负责管理 Linux 中所有的”终端类”设备,覆盖范围很广:

设备类型设备节点说明
串口(UART)/dev/ttymxc0、/dev/ttyS0嵌入式最常用,调试打印、Shell 登录
虚拟终端/dev/tty1~/dev/tty6桌面 Linux 的文字控制台(Ctrl+Alt+F1 切换)
伪终端/dev/pts/0、/dev/ptmxSSH、终端模拟器(xterm、tmux)使用

/dev/ 下对应的设备节点:

# 串口设备(iMX6ULL 上的 UART)
crw-rw---- root dialout 207, 16  /dev/ttymxc0
crw-rw---- root dialout 207, 17  /dev/ttymxc1

# 虚拟终端(桌面 Linux)
crw--w---- root tty      4,  1  /dev/tty1
crw--w---- root tty      4,  2  /dev/tty2

# 伪终端
crw-rw-rw- root tty      5,  2  /dev/ptmx    ← 主端,open 后动态创建从端
crw--w---- root tty    136,  0  /dev/pts/0   ← 从端(SSH、tmux 等使用)


二、分层架构

TTY 子系统由上到下分为四层:

┌─────────────────────────────────────────┐
│          用户空间进程                    │
│   open/read/write/ioctl /dev/ttymxc0   │
└───────────────────┬─────────────────────┘
                    │ 系统调用
┌───────────────────▼─────────────────────┐
│           VFS 文件操作层                 │
│         tty_io.c(tty_fops)            │
└───────────────────┬─────────────────────┘
                    │
┌───────────────────▼─────────────────────┐
│           行规程(Line Discipline)       │
│   n_tty.c(默认 N_TTY)                  │
│   处理:规范模式/原始模式、回显、Ctrl+C  │
└───────────────────┬─────────────────────┘
                    │
┌───────────────────▼─────────────────────┐
│         UART 子系统核心                  │
│         serial_core.c                   │
└───────────────────┬─────────────────────┘
                    │
┌───────────────────▼─────────────────────┐
│         UART Host 驱动                  │
│   imx.c / fsl_lpuart.c(操作寄存器)    │
└───────────────────┬─────────────────────┘
                    │
                  硬件 UART 控制器

嵌入式串口数据流(接收方向):

硬件 UART 收到数据
    │ 触发中断
    ▼
imx.c 中断处理函数
    │ 调用 tty_insert_flip_char()
    ▼
tty_buffer.c 翻转缓冲区(flip buffer)
    │ 调度 workqueue,脱离中断上下文
    ▼
n_tty.c 行规程处理
    │ 规范模式:行缓冲 + 回显;原始模式:直接转发
    ▼
tty_io.c 的 tty_read()
    │ 唤醒阻塞的 read() 调用
    ▼
用户空间 read() 返回数据


三、源码文件总览

以 iMX6ULL 配置编译后,drivers/tty/ 生成的主要文件,按功能分组:

drivers/tty/
├── TTY 核心层
│   ├── tty_io.c           ← 最核心,file_operations 实现
│   ├── tty_ldisc.c        ← 行规程注册与切换管理
│   ├── tty_buffer.c       ← 翻转缓冲区,中断/进程上下文桥梁
│   ├── tty_port.c         ← 串口助手结构(tty_port)
│   ├── tty_ioctl.c        ← IOCTL 接口(TCGETS/TCSETS 等)
│   ├── tty_baudrate.c     ← 波特率转换表
│   ├── tty_jobctrl.c      ← 作业控制(前台/后台进程组、SIGHUP)
│   ├── tty_mutex.c        ← TTY 互斥锁
│   └── tty_ldsem.c        ← 行规程切换专用读写信号量
│
├── 行规程
│   ├── n_tty.c            ← 默认行规程 N_TTY(规范/原始模式)
│   └── n_null.c           ← 空行规程 N_NULL(丢弃所有数据)
│
├── 串口/UART 层(嵌入式核心)
│   └── serial/
│       ├── serial_core.c       ← UART 子系统框架
│       ├── imx.c               ← iMX 系列 UART Host 驱动
│       ├── fsl_lpuart.c        ← Freescale LPUART Host 驱动
│       └── serial_mctrl_gpio.c ← GPIO 模拟硬件流控信号
│
├── 早期控制台
│   └── earlycon.c         ← 内核早期启动打印,完整驱动加载前使用
│
├── 虚拟终端(桌面 Linux,嵌入式通常不涉及)
│   └── vt/
│       ├── vt.c                ← 虚拟终端核心(tty1~tty6)
│       ├── vt_ioctl.c          ← 虚拟终端 IOCTL
│       ├── vc_screen.c         ← 屏幕缓冲区,提供 /dev/vcs*
│       ├── keyboard.c          ← 键盘输入映射
│       ├── defkeymap.c         ← 默认键盘映射表数据
│       ├── consolemap.c        ← 字符编码映射(ASCII/Unicode 转换)
│       ├── consolemap_deftbl.c ← 默认字符映射表数据
│       └── selection.c         ← 虚拟终端文本复制粘贴
│
└── 特殊功能
    ├── pty.c              ← 伪终端(SSH/terminal emulator 使用)
    └── sysrq.c            ← Magic SysRq(Alt+SysRq+key 调试组合键)


四、各文件详解

4.1 TTY 核心层

tty_io.c

TTY 子系统最核心的文件。实现 /dev/ttyX 设备文件的 file_operations(即 tty_fops),包括 tty_open()、tty_read()、tty_write()、tty_release(),是 VFS 与下层行规程之间的桥梁。

tty_ldisc.c

行规程(Line Discipline)的注册和管理。行规程是 TTY 核心层与驱动层之间的处理模块,定义了数据的加工方式(回显、特殊字符处理、缓冲策略等)。提供 tty_ldisc_register()、tty_set_ldisc() 等接口,支持运行时切换。

tty_buffer.c

翻转缓冲区(flip buffer)管理。UART 中断处理函数将接收到的数据放入 flip buffer,然后调度 workqueue 在进程上下文中处理,实现中断上下文与行规程处理的解耦,是高速串口不丢数据的关键机制。

tty_port.c

提供 struct tty_port 辅助结构,封装了串口打开/关闭的引用计数、阻塞打开(等待载波检测 DCD)、唤醒等通用逻辑,减少各 UART Host 驱动的重复代码。

tty_ioctl.c

实现 TTY 设备的 ioctl 接口,处理 POSIX 标准的终端控制命令:

常用 IOCTL说明
TCGETS / TCSETS获取/设置终端参数(struct termios)
TIOCGWINSZ获取终端窗口大小
TIOCMGET / TIOCMSET获取/设置调制解调器控制信号(RTS/CTS 等)
TCSBRK发送 BREAK 信号

tty_baudrate.c

波特率转换表,将 POSIX 波特率常量(B9600、B115200 等)映射为实际数值:

static const speed_t baud_table[] = {
    0, 50, 75, 110, ..., 115200, 230400, 460800,
    500000, 576000, 921600, 1000000, ..., 4000000
};

注意:最高支持 4 Mbps。超过 4M 的波特率无法通过 stty 设置,需要通过 ioctl 直接操作,或修改该表。

tty_jobctrl.c

作业控制(Job Control)实现。管理进程组、会话与控制终端的关系:当 Shell 的控制终端关闭时向前台进程组发 SIGHUP;处理 Ctrl+C(SIGINT)、Ctrl+Z(SIGTSTP)等信号;实现 tcgetpgrp() / tcsetpgrp()。

tty_mutex.c

TTY 子系统全局互斥锁封装,保护对 TTY 数据结构的并发访问。

tty_ldsem.c

行规程读写信号量(ldsem = line discipline semaphore)。不是行编辑或信号处理功能,是一个专用同步原语:切换行规程时必须等待所有正在进行的读写操作完成,同时阻止新的读写进入,tty_ldsem 专门实现这个精确的同步语义。


4.2 行规程

n_tty.c

N_TTY 行规程(编号 0),是所有串口打开后的默认行规程。实现两种工作模式:

模式特点典型用途
规范模式(canonical)以行为单位缓冲;支持退格、删除等行编辑;Ctrl+C 发 SIGINT;Enter 后才交给应用Shell 交互
原始模式(raw)字节直通,不做任何处理;read() 可立即返回单字节串口透传、自定义协议

切换模式通过 tcsetattr() 修改 termios 的 c_lflag 实现。

n_null.c

N_NULL 行规程。与 /dev/null 完全无关,是一个丢弃所有数据的空行规程:向其写入的数据直接丢弃,读取永远返回 0 字节。在需要将 TTY 切换为”静默”状态时使用(/dev/null 的实现在 drivers/char/mem.c)。


4.3 串口 / UART 层

serial_core.c

UART 子系统的核心框架,相当于串口驱动的”核心层”(类比 PWM 的 core.c):

  • 提供 uart_register_driver() / uart_add_one_port(),供 Host 驱动注册
  • 实现串口的 tty_operations,对接 TTY 核心层
  • 统一处理波特率配置、流控、字符发送等通用逻辑
  • 管理 /dev/ttymxc*、/dev/ttyS* 等设备节点的创建

imx.c

iMX 系列 UART 的 Host 驱动,实现 uart_ops:

  • probe():初始化寄存器、申请中断、注册到 serial_core
  • 中断处理:接收时调用 tty_insert_flip_char() 写入 flip buffer;发送时读取队列写寄存器
  • set_termios():根据 termios 参数配置波特率、数据位、停止位、校验位等寄存器

fsl_lpuart.c

Freescale LPUART(Low Power UART)Host 驱动,结构与 imx.c 类似但寄存器定义不同,适用于某些 iMX7/iMX8 变体和 Kinetis 系列芯片。

serial_mctrl_gpio.c

允许用 GPIO 引脚模拟串口硬件流控信号(RTS/CTS/DTR/DSR/DCD/RI)。当硬件 UART 控制器没有专用流控引脚时,可以用普通 GPIO 代替,由该文件提供统一接口。

earlycon.c

早期控制台支持。在内核启动极早期(start_kernel 执行之前),完整的 UART 驱动尚未初始化,earlycon 提供最小化的串口输出能力,用于打印极早期的启动日志。通过 cmdline 参数激活,例如:

earlycon=imx-uart,0x02020000,115200


4.4 虚拟终端

以下文件主要用于桌面 Linux 的文字控制台(/dev/tty1~/dev/tty6),嵌入式开发通常不涉及,了解职责即可。

文件职责
vt.c虚拟终端核心,管理多个文字控制台,处理 VT100/ANSI 转义序列
vt_ioctl.c虚拟终端 IOCTL(切换 VT、设置模式等)
vc_screen.c屏幕缓冲区管理,提供 /dev/vcs* 设备接口
keyboard.c键盘输入:将扫描码翻译为字符/功能键,处理修饰键状态
defkeymap.ckeyboard.c 使用的默认键盘映射表(由 keymaps 文件生成)
consolemap.c控制台字符编码映射,支持不同字符集与 Unicode 的转换
consolemap_deftbl.cconsolemap.c 使用的默认映射表数据
selection.c虚拟终端文本选择与粘贴(配合 GPM 鼠标守护进程)

4.5 特殊功能

pty.c

伪终端(Pseudo Terminal)实现。SSH、tmux、xterm 等都依赖伪终端:

sshd 进程
    │ open("/dev/ptmx")   → 获得主端 fd
    │ grantpt/unlockpt/ptsname → 获得从端路径 /dev/pts/N
    │
    ├─ 主端(ptmx fd)   ← sshd 读写,转发给 SSH 客户端
    └─ 从端(/dev/pts/N)← 远端 Shell 的控制终端

sysrq.c

Magic SysRq 支持(Alt+SysRq+key)。注册了一组内核调试命令:b(reboot)、s(sync)、u(remount ro)、t(show tasks)等。串口调试时可通过发送特定字符序列触发(需开启内核配置 CONFIG_MAGIC_SYSRQ)。


五、嵌入式开发关注重点

对于嵌入式串口开发,各文件重要性排序:

优先级文件原因
★★★imx.c / fsl_lpuart.cHost 驱动,寄存器操作,probe 实现,移植时主要修改这里
★★★serial_core.cUART 框架,理解驱动注册流程和 tty_operations
★★☆n_tty.c行规程,理解规范模式/原始模式、回显、特殊字符处理
★★☆tty_buffer.cflip buffer,理解中断接收的数据流转,排查丢数据问题
★☆☆tty_io.c理解 read/write 系统调用的完整路径
★☆☆earlycon.c移植新平台时,配置早期串口打印

虚拟终端相关文件(vt.c、keyboard.c 等)在纯嵌入式场景下基本不需要深入了解。


关联笔记

延伸阅读

TTY 位于用户空间与串口驱动之间,建议结合启动流程和设备模型理解。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部