1. Base
1.1. 常见术语
术语 | 解释 |
---|---|
Terminal | 终端,是一个电子(或电气)人机交互设备 |
serial Terminal | TTY 设备的一种,通过串口线连接 |
console | 控制台,早期PC的键盘显示器,比TTY 终端拥有更多的权限,系统的运行日志、错误信息通常会输出到控制台 |
第一个Unix终端是一个名字为ASR33的电传打字机,而电传打字机的英文单词为Teletype(或Teletypewritter),缩写为TTY。
1.2. TTY 分类
tty 设备有几个分类:
- console(通常指键盘,显示器)
- serial tty(传统意义的HW,输入输出都在一个独立的硬件上,如peripheral uart)
- VT(virtual tty,传统控制台同一时刻,只能有一个终端使用,为满足多用户、应用,Unix/linux又虚拟出6个终端,可以通过键盘的组合键(CTRL+ALT+ F1~F6)将某一个虚拟终端调出来在屏幕上显示)
- PTY(Pseudo TTY) 伪终端由pts(pseudo terminal slave)和ptm(pseudo terminal master)组成。
PTS: 模拟终端完成与shell等应用的TTY 输入、输出需求
PTM: 将数据通过socket 等形式与真实设备之间进行通行shell <-> PTS <-> PTM <-> SSHD,XTERM->->->
如果从应用上讲还有如下:
- 软件终端(PUTTY, SecureCRT, mobalxterm, xshell等, 由软件模拟终端)
- USB, Ethernet终端(通过其他通信协议模拟终端,例如USB CDC的uart)
- 图形终端(GUI图形也可以说成终端的一种,只是不再是TTY框架中了,TTY主要在字符设备范围)
1 | 设备号(主, 次) 字符设备 备注 |
2. 软件架构
TTY Line Disciplines
line disciplines(线路规程), 可以把它看成设备驱动和应用接口之间的一个适配层, 按照一些定义的标准进行某些字符的转换。例如”\n”, “\r” 之间的转换。在Linux 4.9.x中有这些:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/* include/linux/tty.h, line disciplines */
/* cards about SMS messages */
3. Data Structure
3.1. TTY Core Data Structure
1 | struct tty_struct { |
3.2. Serial Driver Data Structure
相关数据结构, struct uart_driver
用来联系struct tty_driver
, uart_port
则包含了uart 的一些callback(其中大部分参数都是使用struct uart_port) 和固有属性例如:irq, baudrate, fifosize等。
1 | struct uart_state { |
4. Flow
4.1. serial 驱动初始化
串口驱动初始化,主要涉及两个函数:1
2int uart_register_driver(struct uart_driver *drv);
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport);
uart_register_driver()
将uart_driver 透过serial_core
中调用tty_register_driver()
函数注册tty_driver
结构体,同时根据Major, Minor 注册char设备。
之后调用uart_add_one_port()
将uart_port 绑定到uart_state上,并注册device_attribute,并最终调用device_register()
,完成设备模型的注册,最终才会产生”/dev/ttyS0”等设备。
4.1. serial Open Flow
在uart_add_one_port()
->tty_port_register_device_attr()
->tty_cdev_add()
中注册了cdev设备的R/W/Open。1
2
3
4
5
6
7static const struct file_operations tty_fops = {
.read = tty_read,
.write = tty_write,
.unlocked_ioctl = tty_ioctl,
.open = tty_open,
.release = tty_release,
};
因此,从数据结构上观察struct file_operation
-> struct tty_operation
-> struct tty_port_operation
-> struct uart_ops
,如下图:
4.2. serial Read Flow
Read 流程可以分为两个部分, 用户空间与硬件的RX 中断函数。可以理解成消费者与生产者的关系。
4.2.1. Consumer
用户空间调用下来的read, 可以看作是消费者。
4.2.2. Provider
在中断服务程序中将收到的ch,扮演生产者的角色,放置到tty_port.tty_buffer, 如果空间不够则调用__tty_buffer_request_room()
最小分配256 Bytes.
1 | int __tty_insert_flip_char(struct tty_port *port, unsigned char ch, char flag) |
之后调用,tty_flip_buffer_push()
唤醒work 即tty_flip_buffer_push()
, 调用到flush_to_ldisc()
工作队列函数,将tty_buf的数据拷贝到struct n_tty_data.read_buf
,kill_fasync()
负责唤醒用户空间的异步进程,wake_up_interruptible_poll()
唤醒在discipline read 时的读等待队列。
4.3. serial Write Flow
数据流向是userspace data -> tty_struct.write_buf
-> uart_state.xmit
。并且在n_tty.c 中的line routine(discipline)中做了回显的操作。
在Write 的流程中涉及到了如下缓冲区域:
- tty_struct.write_buf (char *)
- uart_state.xmit (circ_buf)
- HW 的TX-FIFO