Linux uart子系统

10 阅读10分钟

Uart子系统

整体学习阶段

  • 阶段 1:基础概念与架构
  1. 了解 TTY 子系统架构 :阅读相关文档和书籍,理解分层结构
  2. 学习核心结构体 :重点掌握 uart_driver 、 uart_port 、 uart_ops 之间的关系
  3. 分析注册流程 :理解 uart_add_one_port 如何将硬件驱动与系统集成
  • 阶段 2:硬件驱动分析
  1. 阅读厂商驱动代码 :以 imx.c 为例,理解:
    • 中断处理流程 ( imx_int 、 imx_rxint 、 imx_txint )
    • 数据发送/接收机制 ( imx_transmit_buffer 、 dma_rx_callback )
    • 串口参数配置 ( imx_set_termios )
  2. 研究 DMA 操作 :理解 imx_uart_dma_init 、 start_rx_dma 等函数
  3. 分析 Console 实现 :理解系统启动时的串口输出机制
  • 阶段 3:行规层与 TTY 核心
  1. 学习 n_tty.c :理解行规层如何处理特殊字符
  2. 分析 tty_io.c :理解 TTY 核心如何管理设备和处理用户请求
  3. 追踪数据流程 :从用户空间写入,经过 TTY 核心、行规层,到硬件驱动的完整流程

附上我的脑图梳理链接:

脑图链接

imx.c驱动分析

1.整体初始化流程:

Linux 串口驱动属于 TTY 子系统的一种 driver,整体初始化流程:

imx_serial_init
 ├─ uart_register_driver()        //注册 UART 驱动框架
 │   ├─ 创建 tty_driver
 │   ├─ 绑定 tty_operations
 │   ├─ tty_register_driver()     //注册字符设备
 │   │   ├─ alloc_chrdev_region   //分配主设备号
 │   │   ├─ tty_cdev_add          //注册 cdev
 │   │   ├─ tty_register_device   //创建 /dev/ttyX
 │   │   └─ proc_tty_register_driver
 │
 └─ platform_driver_register()
     └─ serial_imx_probe()        //设备实例初始化

2.TTY Driver 注册流程

(1)tty_driver结构体代表着一类tty设备,比如/dev/ttyS0和/dev/ttyS1。

(2)绑定操作函数tty_set_operations(normal, &uart_ops)

struct tty_operations uart_ops

static const struct tty_operations uart_ops = {
    .open       = uart_open,
    .close      = uart_close,
    .write      = uart_write,
    .write_room = uart_write_room,
    .chars_in_buffer = uart_chars_in_buffer,
    .ioctl      = uart_ioctl,
};

(3)初始化tty_port

tty_portTTY设备实例状态管理结构

负责

  • open/close计数
  • buffer
  • hangup
  • wakeup

(4)注册字符设备

3.Platform Driver 注册

IMX 串口属于 platform device

struct platform_driver serial_imx_driver
{
    .probe = serial_imx_probe,
    .driver = {
        .name = "imx-uart",
        .of_match_table = imx_uart_dt_ids,
        .pm = &imx_serial_port_pm_ops,
    }
}

主要是probe函数:

1 解析设备树 2 寄存器映射 3 初始化 uart_port, 填充uart_port的属性,比较重要是sport->port.ops = &imx_pops; 4 注册 uart port 5 申请中断

4.数据发送过程

用户空间:

write("/dev/ttymxc0")

调用路径:

write
 ↓
tty_write
 ↓
n_tty_write
 ↓
uart_write
 ↓
uart_start
 ↓
imx_start_tx

TX DMA模式:

imx_start_tx
   ↓
schedule_work(&sport->tsk_dma_tx)

dma_tx_work
   ↓
dmaengine_prep_slave_sg

DMA 完成
   ↓
dma_tx_callback
   ↓
uart_write_wakeup

5.接收流程

DMA初始化:

#imx_startup
imx_uart_dma_init

RX DMA:

imx_startup
   ↓
start_rx_dma
   ↓
dmaengine_prep_dma_cyclic

DMA接收完成:

dma_rx_callback
   ↓
dma_rx_work
   ↓
tty_insert_flip_string
   ↓
tty_flip_buffer_push

6.中断模式(非DMA)

非DMA模式下使用中断,DMA模式下dma_r/tx_callback处理

devm_request_irq(..., imx_int) //申请中断
imx_int //中断处理函数
 ├─ imx_rxint //接收中断处理函数
 └─ imx_txint //发送中断处理函数
 
 #流程
 UART RX interrupt
      ↓
imx_rxint
      ↓
tty_insert_flip_string
      ↓
tty_flip_buffer_push

n_tty.c 驱动分析

1.概述

n_tty.c 是 Linux TTY 子系统中的 N_TTY 行规程(Line Discipline) 实现,它是终端设备最基本、最常用的行规程。行规程位于 TTY 核心和硬件驱动之间,负责处理字符的输入输出、特殊字符处理、换行转换等核心功能。

2.核心数据结构

struct n_tty_data

struct n_tty_data {
    /* 生产者发布 */
    size_t read_head;        // 读缓冲区头指针
    size_t commit_head;      // 已提交数据的头指size_t canon_head;       // 规范模式下的行尾
    指针
    size_t echo_head;        // 回显缓冲区头指针
    size_t echo_commit;      // 已提交的回显数据
    size_t echo_mark;        // 回显标记位置
    DECLARE_BITMAP(char_map, 256);  // 字符映射
    表,用于快速判断特殊字符

    /* 私有变量 */
    unsigned long overrun_time;  // 溢出时间
    int num_overrun;         // 溢出计数

    /* 非原子操作 */
    bool no_room;            // 缓冲区空间不足标/* 标志位 */
    unsigned char lnext:1, erasing:1, raw:1, 
    real_raw:1, icanon:1;
    unsigned char push:1;

    /* 生产者和消费者共享 */
    char read_buf
    [N_TTY_BUF_SIZE];           // 读缓冲区
    DECLARE_BITMAP(read_flags, 
    N_TTY_BUF_SIZE);  // 读标志
    unsigned char echo_buf
    [N_TTY_BUF_SIZE];  // 回显缓冲区

    /* 消费者发布 */
    size_t read_tail;        // 读缓冲区尾指针
    size_t line_start;       // 行开始位置

    /* 受输出锁保护 */
    unsigned int column;     // 当前列位置
    unsigned int canon_column;  // 规范模式下的列
    位置
    size_t echo_tail;        // 回显缓冲区尾指针

    /* 互斥锁 */
    struct mutex atomic_read_lock;  // 原子读锁
    struct mutex output_lock;       // 输出锁
};

3.行规程注册

static struct tty_ldisc_ops n_tty_ops = {
    .magic           = TTY_LDISC_MAGIC,
    .name            = "n_tty",
    .open            = n_tty_open,
    .close           = n_tty_close,
    .flush_buffer    = n_tty_flush_buffer,
    .read            = n_tty_read,
    .write           = n_tty_write,
    .ioctl           = n_tty_ioctl,
    .set_termios     = n_tty_set_termios,
    .poll            = n_tty_poll,
    .receive_buf     = n_tty_receive_buf,
    .write_wakeup    = n_tty_write_wakeup,
    .receive_buf2    = n_tty_receive_buf2,
};

void __init n_tty_init(void)
{
    tty_register_ldisc(N_TTY, &n_tty_ops);
}

4.核心功能模块

  1. 写操作处理 n_tty_write 函数 (L2272):
  • 处理作业控制检查
  • 处理待回显字符
  • 根据 O_POST 标志决定是否进行字符转换
  • 支持阻塞和非阻塞模式
  1. 接收处理 n_tty_receive_buf 函数 (L1737):
  • 处理接收到的字符
  • 根据终端设置进行字符转换
  • 处理特殊字符(中断、退出、挂起等)
  • 管理回显
  • 处理规范模式和非规范模式
  1. 终端参数设置 n_tty_set_termios 函数 (L1763):
  • 响应终端参数变化
  • 更新字符映射表
  • 调整缓冲区状态
  • 设置操作模式(规范/非规范/原始模式)
  1. 特殊字符处理
  • 中断字符 (INTR, Ctrl+C):发送 SIGINT 信号
  • 退出字符 (QUIT, Ctrl+):发送 SIGQUIT 信号
  • 挂起字符 (SUSP, Ctrl+Z):发送 SIGTSTP 信号
  • 删除字符 (ERASE, Ctrl+?):删除最后一个字符
  • 删除行 (KILL, Ctrl+U):删除整行
  • 文件结束 (EOF, Ctrl+D):结束输入
  • 开始/停止字符 (START/STOP, Ctrl+Q/Ctrl+S):控制输出流
  1. 缓冲区管理
  • 读缓冲区 :存储接收到的字符
  • 回显缓冲区 :存储待回显的字符和操作
  • 缓冲区水位 :控制接收和发送的流量

5.工作流程

输入处理流程

  1. 硬件驱动接收字符
  2. 调用 tty_insert_flip_char 将字符放入 flip 缓冲区
  3. 调用 tty_flip_buffer_push 通知 TTY 核心
  4. TTY 核心调用行规程的 receive_buf 方法
  5. n_tty_receive_buf 处理字符:
    • 字符转换(CR/LF 等)
    • 特殊字符处理
    • 回显处理
    • 放入读缓冲区
  6. 唤醒等待读取的进程

输出处理流程

  1. 用户调用 write 系统调用
  2. TTY 核心调用 n_tty_write
  3. n_tty_write 处理:
    • 作业控制检查
    • 处理待回显字符
    • 字符转换(如果 O_POST 启用)
    • 调用底层驱动的 write 方法
  4. 底层驱动发送数据到硬件

6.与其他层的关系

┌─────────────────┐
│   用户空间      │  read()/write()
└────────┬────────┘
         │
┌────────▼────────┐
│   tty_io.ctty_read()/tty_write()
└────────┬────────┘
         │
┌────────▼────────┐
│   n_tty.c       │  行规程层
│                 │  - 字符转换
│                 │  - 特殊字符处理
│                 │  - 回显处理
│                 │  - 行编辑
└────────┬────────┘
         │
┌────────▼────────┐
│   硬件驱动      │  如 imx.c
│   (uart_driver) │  - 硬件操作
└────────┬────────┘
         │
┌────────▼────────┐
│   硬件层        │  UART 控制器
└─────────────────┘

7.关键函数分析

1.n_tty_write

功能 :处理写操作,支持字符转换和回显。

核心流程 :

  1. 作业控制检查
  2. 处理待回显字符
  3. 如果 O_POST 启用:
    • 调用 process_output_block 处理块输出
    • 调用 process_output 处理单个字符
  4. 否则直接调用底层驱动的 write 方法

处理阻塞/非阻塞模式

2.n_tty_receive_buf

功能 :处理接收到的字符。

核心流程 :

  1. 调用 n_tty_receive_buf_common 处理接收数据
  2. 处理特殊字符(break、parity error、overrun)
  3. 根据终端设置进行字符转换
  4. 将处理后的数据放入读缓冲区
  5. 唤醒等待读取的进程

3.n_tty_set_termios

功能 :响应终端参数变化。

核心流程 :

  1. 更新字符映射表 char_map
  2. 根据 ICANON、ISTRIP、IXON 等标志位设置处理模式
  3. 重置规范模式的行缓冲
  4. 设置 raw/real_raw 标志

4.n_tty_open

功能 :打开行规程。

核心流程 :

  1. 分配并初始化 n_tty_data 结构
  2. 重置缓冲区状态
  3. 设置初始标志

5.n_tty_close

功能 :关闭行规程。

核心流程 :

  1. 释放 n_tty_data 结构
  2. 清理资源

8.缓冲区管理机制

读缓冲区

  • 大小 : N_TTY_BUF_SIZE (通常 8192 字节)
  • 操作 :
    • read_head :生产者(接收)写入位置
    • read_tail :消费者(读取)读取位置
    • commit_head :已提交的读数据位置
    • canon_head :规范模式下的行结束位置

回显缓冲区

  • 用于存储待回显的字符
  • 支持特殊的回显操作码(ERASE、KILL 等)
  • 管理回显位置和状态

9.模式分类

  1. 规范模式 (Canonical Mode / ICANON)
  • 每次读取一行(以换行符为结束标志)
  • 支持行编辑(ERASE、KILL 等)
  • 支持回显
  • 支持信号生成(INTR、QUIT、SUSP)
  1. 非规范模式 (Non-Canonical Mode)
  • 读取固定数量的字符或超时
  • 不支持行编辑
  • 常用于串口通信
  • 可配置最小字符数和超时时间
  1. 原始模式 (Raw Mode)
  • 禁用所有特殊处理
  • 字符直接传递
  • 无回显
  • 无信号生成
  • 常用于需要精确控制的应用

n_tty.c 是 Linux TTY 子系统的核心组件,它实现了 N_TTY 行规程,负责处理终端的输入输出、特殊字符处理、行编辑等功能。它是连接用户空间和硬件驱动的桥梁,为终端设备提供了标准的操作接口。

理解 n_tty.c 对于理解整个 TTY 子系统的工作原理至关重要,它展示了如何在操作系统中实现一个完整的行规程,包括字符处理、缓冲区管理、特殊字符处理等核心功能。

tty_io.c驱动分析

关键函数分析

1. tty_open

功能 :打开 TTY 设备。

核心流程 :

  1. 检查设备状态
  2. 分配和初始化 tty_struct
  3. 绑定到 tty_driver
  4. 初始化行规程
  5. 处理控制终端设置
  6. 调用驱动的 open 方法

2. tty_read

功能 :读取 TTY 设备数据。

核心流程 :

  1. 检查 TTY 状态
  2. 调用行规程的 read 方法
  3. 处理阻塞和非阻塞模式
  4. 管理信号和中断

3. tty_write

功能 :写入 TTY 设备数据。

核心流程 :

  1. 检查 TTY 状态
  2. 调用行规程的 write 方法
  3. 处理阻塞和非阻塞模式
  4. 管理信号和中断

4. tty_ioctl

功能 :处理 TTY 控制命令。

核心流程 :

  1. 解析命令类型
  2. 调用行规程或驱动的 IO 控制方法
  3. 处理终端参数设置和查询

5. tty_hangup

功能 :处理 TTY 设备的挂断事件。

核心流程 :

  1. 调度挂断工作
  2. 调用 __tty_hangup 处理实际挂断
  3. 通知相关进程
  4. 清理资源

缓冲区管理

Flip 缓冲区

  • 功能 :临时存储硬件驱动接收到的数据
  • 操作 :
    • tty_insert_flip_char :插入字符到 flip 缓冲区
    • tty_flip_buffer_push :通知 TTY 核心处理 flip 缓冲区

行规程缓冲区

  • 功能 :存储行规程处理后的数据
  • 操作 :由行规程管理(如 n_tty.c 中的 read_buf)

信号处理

  • SIGHUP :当 TTY 设备挂断时发送
  • SIGINT :当用户按下 Ctrl+C 时发送
  • SIGQUIT :当用户按下 Ctrl+\ 时发送
  • SIGTSTP :当用户按下 Ctrl+Z 时发送