程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

linux驱动移植-tty架构

----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

一、终端(tty)

1.1 回顾

还记的在Mini2440之uboot移植流程之linux内核启动分析(六)小节中,我们介绍过u-boot在启动linux之前,需要做一些准备工作,比如配置启动参数:

#define CONFIG_BOOTARGS        "root=/dev/mtdblock3 console=ttySAC0,115200 init=/linuxrc"

其中:

  • root 指定文件系统位置,这里指定为NAND三号分区;
  • init 指定内核启动后执行的第一个应用程序;
  • console 指定控制台使用哪个终端,计算机启动的时候,会将内核启动日志输出到控制台,这里的ttySAC0指的就是串口0;

console参数用来指定控制台使用哪个终端,控制台是与系统交互的设备,linux操作系统会接收来自控制台的输入,同时将信息输出到控制台上。

那终端又是什么?终端就是处理计算机输入输出的一套设备,它用来显示主机运算的输出,并且接受主机要求的输入,典型的终端包括电传打印机、显示器键盘套件。

ttySAC0中tty是什么,tty是Teletype或Teletypewriter 的缩写,原来是指电传打字机,后来这种设备逐渐键盘和显示器取代。不管是电传打字机还是键盘显示器,都是作为计算机的终端设备存在的,所以tty也泛指计算机的终端(terminal)设备。

如果不指定console,默认设置为/dev/tty0,tty0表示当前所使用虚拟终端的一个别名。虚拟终端一共有6个,在文件系统中使用/dev/tty1~/dev/tty6表示,其输入来自键盘、同时将tty驱动的输出到显示器,他们实际上是共享同一个真实的物理控制台(也就是连接在计算机上的键盘和显示器套件)。

当然这里指定为ttySAC0,表示计算机启动的时候,内核启动日志会通过串口0输出,同时通过串口0接收用户的输入。

1.2 物理终端terminal

早期的终端(terminal) 是一台独立于计算机的机器(teleprinter即, tty),大概像下面的样子:

它终端通过线缆与计算机连接,作为输入和输出设备,将输入的数据发送到计算机, 并打印出响应。

 

在今天你很难想象程序的运行结果需要等到打印出来才能看到,teleprinter设备已经进了计算机博物馆。现在我们用tty代表计算机终端(terminal),只是沿用了历史习惯,电传打字机(teletypewriter)曾经是计算机的终端,它的缩写便是tty。

从历史上看,终端刚开始就是电传打印机,配有打印机,键盘,带有一个串口,通过串口传送数据到主机端,然后主机处理完交给终端打印出来。

为了把不同型号的电传打字机接入计算机,需要在操作系统内核安装驱动,为上层应用屏蔽所有的低层细节。

如上图所示,其中:

  • uart驱动:物理终端通过电缆连接到计算机上的 uart(通用异步接收器和发射器)。操作系统中有一个uart驱动程序用于管理字节的物理传输;
  • 行规范:上图中内核中的 Line discipline(行规范)用来提供一个编辑缓冲区和一些基本的编辑命令(退格,清除单个单词,清除行,重新打印),主要用来支持用户在输入时的行为(比如输错了,需要退格);
  • tty驱动:tty驱动用来进行会话管理,并且处理各种终端设备;

uart驱动、行规范和 tty驱动都位于内核中,它们的一端是终端设备,另一端是用户进程。因为在linux下所有的设备都是文件,所以它们三个加在一起被称为 "tty设备"。

1.3 控制台console

控制台的概念与终端含义非常相近,其实现在我们经常用它们表示相同的东西,但是在计算机的早期时代,它们确实是不同的东西。

一些数控设备(比如数控机床)的控制箱,通常会被称为控制台,顾名思义,控制台就是一个直接控制设备的面板,上面有很多控制按钮。

在计算机里,把那套直接连接在电脑上的键盘和显示器就叫做控制台。而终端是通过串口连接上的,不是计算机自身的设备,而控制台是计算机本身就有的设备,一个计算机只有一个控制台。

在linux系统中,/dev/console是系统控制台,是与操作系统交互的设备。计算机启动的时候,所有的信息都发送到该设备上

1.4 虚拟控制台

今天电传打字机已经进了博物馆,但linux挺仍然保留了当初tty驱动和Line discipline的设计和功能。终端不再是一个需要通过uart连接到计算机上物理设备。终端成为内核的一个模块,它可以直接向tty驱动发送字符,并从tty驱动读取响应然后打印到屏幕上。也就是说,用内核模块模拟物理终端设备,因此被称为终端模拟器(terminal emulator),或者说虚拟终端(virtual console)、在其他博客上看到的本地终端指的也是这个。 

上图是一个典型的Linux系统,虚拟终端就像过去的物理终端一样,它监听来自键盘的事件将其发送到tty驱动,并从tty驱动读取响应,通过显卡驱动将结果渲染到显示器上。tty驱动和Line discipline的行为与原先一样,但不再有UART和物理终端参与。

如何看到一个虚拟终端呢,由于在linux系统中,一切设备都是文件,虚拟终端也不例外。

/dev/tty1~/dev/tty6是这些虚拟终端在文件系统中的表示,由于这些虚拟终端的输入数据来自键盘、同时将tty驱动的输出到显示器,因此他们实际上是共享同一个真实的物理控制台。

假设你的linux系统没安装图形界面(或者默认不启用图形界面),当系统启动完成之后,你会在屏幕上看到一个文本模式的登录提示。这个界面就是虚拟终端的界面。我们可以通过CTRL+ALT+[F1~F6]组合键,在这几个虚拟终端之间切换,每切换到一个虚拟终端,这个虚拟终端就是当前的焦点终端:

该焦点终端会被内核记录为全局变量,这样只要有键盘输入,就会把输入的字符交给焦点终端。

系统中有没有什么变量可以表示焦点终端呢?当然有了,那就是/dev/console,不管你在哪里往/dev/console里写东西,这些东西总会出现在系统当前的焦点终端上!

那么系统中有没有一个叫做自己的全局变量呢?当然有,那就是/dev/tty,也就是说,无论你在哪个终端下工作,当你往/dev/tty里写东西的时候,它总是会马上出现在你的眼前。

1.5 伪终端pty

伪终端(pseudo terminal, pty)是终端的发展,为满足现在需求(比如网络登陆、xwindow窗口的管理)。它是成对出现的逻辑终端设备(即master和slave设备, 对master的操作会反映到slave上)。它是运行在用户态的终端模拟程序。

pty运行在用户态,更加安全和灵活,同时仍然保留了tty驱动和Line discipline的功能。常用的伪终端有 xterm,gnome-terminal,以及远程终端ssh。

注意:X11也叫做X Window系统,是图形化窗口管理系统 。它是诞生于Unix 、以及 OpenVMS,是传统上Unix环境中建立图形用户界面的标准工具包和协议。linux操作系统下的图形管理界面(GNOME、KDE)也是基于X11运行库基础上开发的。在图例中,X窗口系统从键盘、鼠标获取输入信息,之后将输入反馈显示于屏幕,同时把输入输出转交给伪终端(这样看来,伪终端实际上就是一个应用层的程序,类似于我们的浏览器等软件)。

pty是通过打开特殊的设备文件/dev/ptmx 创建,由一对双向的字符设备构成,称为pty master 和pty slave。其中:

  • pty slave对应文件系统/dev/pts/目录下的一个文件;
  • pty master则在内存中标识为一个文件描述符(fd);

我们以ubuntu桌面版提供的gnome-terminal为例,介绍伪终端如何与tty驱动交互。当我们在ubuntu桌面启动gnome-terminal时:

  • gnome-terminal通过打开/dev/ptmx文件创建了一个伪终端的master和slave对,它持有pty master的文件描述符/dev/ptmx,并把GUI绘制在显示器上;
  • gnome-terminal会fork一个shell子进程bash,并让bash持有pty slave的设备文件/dev/pts/[n],bash 的标准输入、标准输出和标准错误都设置为pty slave;
  • gnome-terminal 负责监听键盘事件,并将输入的字符发送到pty master;
  • Line discipline收到字符,进行缓冲。只有按下回车键时,它才会把缓冲的字符复制到pty slave;
    • gnome-terminal只会在屏幕上显示来自pty master的东西。因此,Line discipline 需要回传字符,以便让你看到你刚刚输入的内容;
  • 当按下回车键时,tty驱动负责将缓冲的数据复制到pty slave;
  • bash从标准输入读取输入的字符(例如 ls -l )。注意,bash 在启动时已经将标准输入被设置为了pty slave;
  • bash解释从输入读取的字符,发现需要运行ls;
  • bash fork出ls进程。bash fork出的进程拥有和bash相同的标准输入、标准输出和标准错误,也就是pty slave;
  • ls运行,结果打印到标准输出,也就是pty slave;
  • tty驱动将字符复制到pty  master;
  • gnome-terminal循环从pty master读取字节,绘制到用户界面上;

注意:如果我们采用cat /dev/pts/xx去读取pty salve,实际上也是可以将Line discipline缓冲区数据读取出来的,不过这样会产生竞争关系,后面的示例会说明。同理采用echo "hello slave" > /dev/pts/xx去写入pty slave,实际上也是可以将数据写入到Line discipline缓冲区。

在gnome-terminal中执行tty命令,可以查看目前使用的终端机的文件名称:

zhengyang@zhengyang:~$ tty
/dev/pts/6 

 执行 ps -l (用来查看自己的bash相关的进程)命令,也可以确认shell关联的伪终端是 pts/6:

zhengyang@zhengyang:~$ ps -l
F S   UID    PID   PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1000  91950  91944  0  80   0 -  6128 wait   pts/6    00:00:00 bash
0 R  1000 101980  91950  0  80   0 -  7664 -      pts/6    00:00:00 ps

注意到tty这一列指出了当前进程的终端是pts/6。

1.5.1 /dev/ptmx

/dev/ptmx是一个字符设备文件,当进程打开/dev/ptmx文件时,进程会同时获得一个指向 pseudoterminal master(ptm)的文件描述符和一个在/dev/pts目录中创建的 pseudoterminal slave(pts) 设备。

通过打开/dev/ptmx 文件获得的每个文件描述符都是一个独立的ptm,它有自己关联的pts,ptmx(可以认为内存中有一个ptmx对象)在内部会维护该文件描述符和pts的对应关系,对这个文件描述符的读写都会被ptmx转发到对应的 pts。

我们可以通过lsof命令查看ptmx打开的文件描述符:

1.5.2 shell

前面我们所说的terminal,本质上是基于文本的输入输出机制,它并不理解具体的命令及其语法。

于是就需要引入shell这个玩意,shell 负责解释你输入的命令,并根据你输入的命令,执行某些动作(包括:启动其它进程)。

shell是用户空间的应用程序,通常由terminal fork出来,是 terminal 的子进程。shell用来提示用户输入,解释用户输入的字符,然后处理来自底层操作系统的输出。

通常我们使用较多的shell有bash、csh、flsh、zsh和ksh。如今影响力最大的shell是bash(没有之一)。其名称源自“Bourne-again shell”,是GNU社区对Bourne shell的重写,使之符合自由软件(GPL 协议)。

假设你想看一下/home这个目录下有哪些子目录,可以在shell 中运行了如下命令

ls /home

当你输入这串命令并敲回车键,shell会拿到这一行,然后它会分析出,空格前面的ls是一个外部命令,空格后面的/home是该命令的参数。然后shell会启动这个外部命令对应的进程,并把上述参数作为该进程的启动参数。

刚才提到了【外部命令】这个词汇,顺便解释一下:通俗地说,“内部命令”就是内置在shell中的命令;而“外部命令”则对应了某个具体的【可执行文件】。

当你在shell中执行“外部命令”,shell会启动对应的可执行文件,从而创建出一个“子进程”;而如果是“内部命令”,就【不】产生子进程。

那么,如何判断某个命令是否为“外部命令”?
比较简单的方法是——用如下方式来帮你查找。如果某个命令能找到对应的可执行文件,就是“外部命令”;反之则是“内部命令”。

whereis 命令名称
1.5.3 配置tty设备

内核使用tty驱动来处理terminal和shell之间的通信。Line discipline是tty的一个逻辑组件。Line discipline主要有以下功能:

  • 用户输入时,字符会被回传到pty master;
  • Line discipline会在内存中缓冲这些字符。当用户按回车键时,它才将这些字符发送到pty slave;
  • Line discipline 可以拦截处理一些特殊的功能键,例如:
    • 当用户按ctrl+c时,它向连接到pty slave的进程发送kill -2(SIGINT)信号;
    • 当用户按ctrl+w 时,它删除用户输入的最后一个字;
    • 当用户按ctrl+z 时,它向连接到pyt slave的进程发送kill -STOP信号;
    • 当用户按退格键时,它从缓冲区中删除该字符,并向pty master发送删除最后一个字符的指令;

我们可以使用命令行工具stty查询和配置tty,包括Line discipline规则。在 termina 执行stty -a命令:

root@zhengyang:~# stty -a
speed 38400 baud; rows 65; columns 121; line = 0;
intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q;
stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc

可以通过stty raw命令来禁用所有的Line discipline规则,这样的终端被称为raw terminal。像vi这样的编辑器会将终端设置为raw ,因为它需要自己处理字符。下面介绍的远程终端也是需要一个raw terminal,同样会禁用所有的Line discipline规则。

1.5.4 远程终端ssh

我们经常通过ssh连接到一个远程主机,这时候远程主机上的ssh server 就是一个伪终端pty,它同样持有 pty master,但 ssh server不再监听键盘事件,以及在屏幕上绘制输出结果,而是通过tcp连接,向ssh client发送或接收字符。

1.5.5 示例

一般情况下我们通过远程连接的方式执行命令时,进程的标准输入、标准输出和标准错误输出都会绑定到伪终端上,下面是一个简单的demo程序:

复制代码
#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("PID : %d\n", getpid());
    sleep(200);  // 单位秒

    printf("\n");
    return 0;
}
复制代码

把这段代码保存在文件mydemo.c中,然后执行下面的命令编译并执行该程序:

demo程序输出了自己进程的PID,现在另外开一个终端执行lsof 命令:

可以看到进程的0u(标准输入)、1u(标准输出)、2u(标准错误输出)都绑定到了伪终端 /dev/pts/20上。

注:FD为文件描述符。NAME为文件系统挂载点名称。

下面,我们思考一个问题,如果我们尝试去读取一个/dev/pty/xx设备,将会产生什么影响,我们打开两个终端,并在右侧终端输入1-9这几个数字:

我们发现右侧终端输入的数字只显示了12589,另一半数字显示在了左侧的终端。这主要是因为/dev/pts/21除了被右侧的bash设置为标准输入、标准输出、标准错误输出外,还被左侧终端打开的cat进程设置为输入,我们使用lsof名称查看/dev/pts/21文件被哪些进程打开了:

由于除了右侧终端fork的子进程bash外,还存在左侧终端cat进程读取dev/pts/21,因此产生了竞争关系,导致无法预测输入内容被哪个进程读取到。一旦被cat读到了,那么按下的键将不会显示在当前的终端中。

我们可以通过w命令看看有哪些人登录在机器上,然后去cat他们的/dev/pts/xxx,他们一定会以为自己的键盘坏了!(小提示,当用户登录的时候,使用的/dev/pts/xx文件权限将设置为仅自己读写,owner 设置为自己,所以这个恶作剧必须要 root 才行!)。

1.6 总结

至此我们可以得出这样的结论:现在所说的终端已经不是物理终端了,而是软件模拟终端。如今的终端大致可以分为以下几类:

  • 控制台:/dev/console是系统控制台终端,用于与操作系统交互;/dev/console是一个虚拟的设备,它需要映射到真正的tty上,比如通过内核启动参数“console=ttySCA0”就把console映射到了串口0;
  • 虚拟终端:/dev/ttyn,是运行在内核态的软件模拟终端;当用户在无图形界面的linux系统登录时,使用的是虚拟终端,使用CTRL + ALT+ [F1 - F6]组合键时,我们就可以切换到tty1、tty2、tty3等上面去;
  • 伪终端:/dev/pts/xx, 是运行在用户态的软件模拟终端,比如远程登录ssh、以及我们所使用的ubuntu系统图形界面中的终端;
  • 串行端口终端:dev/ttySn,这一类是使用计算机串行端口连接的终端设备;

二、tty驱动框架

tty子系统框架如下图:

tty驱动框架主要包括:

  • tty core:称之为tty核心,主要是向用户提供统一的软件接口,比如tty_register_device、tty_register_driver函数;
  • Line discipline:称之为tty线路规程,主要从上下两层接收数据,并按照一定协议进行转换,比如ppp或者蓝牙协议;
  • tty driver:该层主要用于实现各类终端的驱动,用以控制实际硬件设备,用于收发数据;

三、tty核心数据结构

学习tty驱动,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。

3.1 struct tty_driver

struct tty_driver用来描述一个tty驱动,定义在include/linux/tty_driver.h:

复制代码
struct tty_driver {
        int     magic;          /* magic number for this structure */
        struct kref kref;       /* Reference management */
        struct cdev **cdevs;
        struct module   *owner;
        const char      *driver_name;
        const char      *name;
        int     name_base;      /* offset of printed name */
        int     major;          /* major device number */
        int     minor_start;    /* start of minor device number */
        unsigned int    num;    /* number of devices allocated */
        short   type;           /* type of tty driver */
        short   subtype;        /* subtype of tty driver */
        struct ktermios init_termios; /* Initial termios */
        unsigned long   flags;          /* tty driver flags */
        struct proc_dir_entry *proc_entry; /* /proc fs entry */
        struct tty_driver *other; /* only used for the PTY driver */

        /*
         * Pointer to the tty data structures
         */
        struct tty_struct **ttys;
        struct tty_port **ports;
        struct ktermios **termios;
        void *driver_state;

        /*
         * Driver methods
         */

        const struct tty_operations *ops;
        struct list_head tty_drivers;
} __randomize_layout;
复制代码

其中部分参数含义如下:

  • magic:表示这个给这个结构体的“幻数”,设为TTY_DRIVER_MAGIC,在alloc_tty_driver函数中被初始化;
  • owner:这个驱动的模块拥有者;
  • cdevs:字符设备指针数组,每个元素都是一个字符设备指针,这些字符设备的文件操作集被设置为tty_fops;数组长度为num;
  • driver_name:驱动名称;
  • name:驱动的设备节点名称;
  • major:主设备号;
  • minor_start:开始次设备号;
  • num:分配的字符设备数量,这字符设备具有相同的主设备号,仅仅次设备号不一样;
  • type:tty驱动的类型;设备类型包括TTY_DRIVER_TYPE_PTY、TTY_DRIVER_TYPE_SERIAL、TTY_DRIVER_TYPE_CONSOLE、TTY_DRIVER_TYPE_SYSTEM;
  • subtype:tty驱动的子类型;
  • init_termios:初始化线路设置;为一个termios结构体,这个成员被用来提供一个线路设置集合;
  • termios:用于保存当前的线路设置,这些线路设置控制当前波特率、数据大小、数据流控设置等;
  • flags:tty驱动标志;
  • driver_state:保存tty驱动私有数据;
  • proc_entry:proc文件系统入口;
  • other:仅对pty驱动又意义;
  • ttys:指针数组,数组长度为num;
    ports:指针数组,数组长度为num;
  • termios:指针数组,数组长度为num;
  • ops:tty字符设备文件操作集;
  • tty_drivers:双向链表节点,用于构建双向链表,注册tty_driver时会将tty_driver添加到双向链表tty_drivers;

3.2 struct tty_struct

strutc tty_struct结构体用于描述tty设备。定义在include/linux/tty.h:

复制代码
struct tty_struct {
        int     magic;
        struct kref kref;
        struct device *dev;
        struct tty_driver *driver;
        const struct tty_operations *ops;
        int index;

        /* Protects ldisc changes: Lock tty not pty */
        struct ld_semaphore ldisc_sem;
        struct tty_ldisc *ldisc;

        struct mutex atomic_write_lock;
        struct mutex legacy_mutex;
        struct mutex throttle_mutex;
        struct rw_semaphore termios_rwsem;
        struct mutex winsize_mutex;
        spinlock_t ctrl_lock;
        spinlock_t flow_lock;
        /* Termios values are protected by the termios rwsem */
        struct ktermios termios, termios_locked;
        struct termiox *termiox;        /* May be NULL for unsupported */
        char name[64];
        struct pid *pgrp;               /* Protected by ctrl lock */
        struct pid *session;
        unsigned long flags;
        int count;
        struct winsize winsize;         /* winsize_mutex */
        unsigned long stopped:1,        /* flow_lock */
                      flow_stopped:1,
                      unused:BITS_PER_LONG - 2;
        int hw_stopped;
        unsigned long ctrl_status:8,    /* ctrl_lock */
                      packet:1,
                      unused_ctrl:BITS_PER_LONG - 9;
        unsigned int receive_room;      /* Bytes free for queue */
        int flow_change;

        struct tty_struct *link;
        struct fasync_struct *fasync;
        wait_queue_head_t write_wait;
        wait_queue_head_t read_wait;
        struct work_struct hangup_work;
        void *disc_data;
        void *driver_data;
        spinlock_t files_lock;          /* protects tty_files list */
        struct list_head tty_files;

#define N_TTY_BUF_SIZE 4096

        int closing;
        unsigned char *write_buf;
        int write_cnt;
        /* If the tty has a pending do_SAK, queue it here - akpm */
        struct work_struct SAK_work;
        struct tty_port *port;
} __randomize_layout;
复制代码

其中部分参数含义如下:

  • flags:标示tty设备的当前状态,包括TTY_THROTTLED、TTY_IO_ERROR、TTY_OTHER_CLOSED、TTY_EXCLUSIVE、 TTY_DEBUG、TTY_DO_WRITE_WAKEUP、TTY_PUSH、TTY_CLOSING、TTY_DONT_FLIP、 TTY_HW_COOK_OUT、TTY_HW_COOK_IN、TTY_PTY_LOCK、TTY_NO_WRITE_SPLIT等;
  • dev:设备驱动模型中的设备,设备的设备号和driver->cdevs[index]字符设备一致,可以将tty_struct看做为device的子类;
  • driver:指向与之关联的tty_driver;
  • index:存储在driver->ttys指针数组的第几个成员;
  • termios_rwsem:tty设备读写信号量,用于实现进程同步问题,解决多个进程同时读写同一个tty设备的资源竞争问题;
  • ldisc_sem:tty设备线路规程信号量,同一时间只能一个进程获取到线路规程ldisc;
  • ldisc:tty设备的线路规程;
  • ops:tty字符设备文件操作集;
  • write_wait:写等待队列头,头节点为struct wait_queue_head_t类型。等待队列中的元素为struct wait_queue_t类型;
  • read_wait:读等待队列头,头节点为struct wait_queue_head_t类型。等待队列中的元素为struct wait_queue_t类型;
  • stopped:是否停止tty设备;
  • write_buf:写缓冲区;
  • write_cnt:写缓冲区大小;
  • driver_data、disc_data为数据指针,用于存储tty驱动和线路规程的“私有”数据。
3.2.1 struct n_tty_data

disc_data存放时tty设备接收到的数据,其数据类型为struct n_tty_data:

复制代码
struct n_tty_data {
        /* producer-published */
        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);

        /* private to n_tty_receive_overrun (single-threaded) */
        unsigned long overrun_time;
        int num_overrun;

        /* non-atomic */
        bool no_room;

        /* must hold exclusive termios_rwsem to reset these */
        unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
        unsigned char push:1;

        /* shared by producer and consumer */
        char read_buf[N_TTY_BUF_SIZE];
        DECLARE_BITMAP(read_flags, N_TTY_BUF_SIZE);
        unsigned char echo_buf[N_TTY_BUF_SIZE];

        /* consumer-published */
        size_t read_tail;
        size_t line_start;

        /* protected by output lock */
        unsigned int column;
        unsigned int canon_column;
        size_t echo_tail;

        struct mutex atomic_read_lock;
        struct mutex output_lock;
};
复制代码

定义的read_buf缓冲区是线性数组,但是却是作为环形缓冲区使用的;

read_head成员是环形缓冲区空闲位置的开始,产生数据的进程从read_head位置开始往缓冲区写入数据;

read_tail成员是环形缓冲区保存数据位置的开始,读取数据的进程从read_tail位置开始从缓冲区读取数据;

tty->read_buf[]    // 环形缓冲区;
tty->read_tail    // 指向缓冲区当前可以读取的第一个字符;
tty->read_head    // 指向缓冲区当前可以写入的第一个地址;

3.3 struct tty_port

strutc tty_port用于描述一个端口。定义在include/linux/tty.h:

复制代码
struct tty_port {
        struct tty_bufhead      buf;            /* Locked internally */
        struct tty_struct       *tty;           /* Back pointer */
        struct tty_struct       *itty;          /* internal back ptr */
        const struct tty_port_operations *ops;  /* Port operations */
        const struct tty_port_client_operations *client_ops; /* Port client operations */
        spinlock_t              lock;           /* Lock protecting tty field */
        int                     blocked_open;   /* Waiting to open */
        int                     count;          /* Usage count */
        wait_queue_head_t       open_wait;      /* Open waiters */
        wait_queue_head_t       delta_msr_wait; /* Modem status change */
        unsigned long           flags;          /* User TTY flags ASYNC_ */
        unsigned long           iflags;         /* Internal flags TTY_PORT_ */
        unsigned char           console:1,      /* port is a console */
                                low_latency:1;  /* optional: tune for latency */
        struct mutex            mutex;          /* Locking */
        struct mutex            buf_mutex;      /* Buffer alloc lock */
        unsigned char           *xmit_buf;      /* Optional buffer */
        unsigned int            close_delay;    /* Close port delay */
        unsigned int            closing_wait;   /* Delay for output */
        int                     drain_delay;    /* Set to zero if no pure time
                                                   based drain is needed else
                                                   set to size of fifo */
        struct kref             kref;           /* Ref counter */
        void                    *client_data;
};
复制代码

3.4 struct tty_operations

tty_operations中的成员函数与 tty_driver中的同名成员函数意义完全一致,描述的是tty字符设备文件操作集,其定义在include/linux/tty_driver.h:

复制代码
struct tty_operations {
        struct tty_struct * (*lookup)(struct tty_driver *driver,
                        struct file *filp, int idx);
        int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
        void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
        int  (*open)(struct tty_struct * tty, struct file * filp);
        void (*close)(struct tty_struct * tty, struct file * filp);
        void (*shutdown)(struct tty_struct *tty);
        void (*cleanup)(struct tty_struct *tty);
        int  (*write)(struct tty_struct * tty,
                      const unsigned char *buf, int count);
        int  (*put_char)(struct tty_struct *tty, unsigned char ch);
        void (*flush_chars)(struct tty_struct *tty);
        int  (*write_room)(struct tty_struct *tty);
        int  (*chars_in_buffer)(struct tty_struct *tty);
        int  (*ioctl)(struct tty_struct *tty,
                    unsigned int cmd, unsigned long arg);
        long (*compat_ioctl)(struct tty_struct *tty,
                             unsigned int cmd, unsigned long arg);
        void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
        void (*throttle)(struct tty_struct * tty);
        void (*unthrottle)(struct tty_struct * tty);
        void (*stop)(struct tty_struct *tty);
        void (*start)(struct tty_struct *tty);
        void (*hangup)(struct tty_struct *tty);
        int (*break_ctl)(struct tty_struct *tty, int state);
        void (*flush_buffer)(struct tty_struct *tty);
        void (*set_ldisc)(struct tty_struct *tty);
        void (*wait_until_sent)(struct tty_struct *tty, int timeout);
        void (*send_xchar)(struct tty_struct *tty, char ch);
        int (*tiocmget)(struct tty_struct *tty);
        int (*tiocmset)(struct tty_struct *tty,
                        unsigned int set, unsigned int clear);
        int (*resize)(struct tty_struct *tty, struct winsize *ws);
        int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
        int (*get_icount)(struct tty_struct *tty,
                                struct serial_icounter_struct *icount);
        int  (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
        int  (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
        void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
        int (*poll_init)(struct tty_driver *driver, int line, char *options);
        int (*poll_get_char)(struct tty_driver *driver, int line);
        void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
        int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;
复制代码

3.5 struct tty_ldisc

struct tty_ldisc定义在include/linux/tty_ldisc.h:

struct tty_ldisc {
        struct tty_ldisc_ops *ops;
        struct tty_struct *tty;
};

tty线路规程是由tty_ldisc_ops和tty_struct组成,其中tty_ldisc_ops为线路规程操作集,定义如下:

复制代码
struct tty_ldisc_ops {
        int     magic;
        char    *name;
        int     num;
        int     flags;

        /*
         * The following routines are called from above.
         */
        int     (*open)(struct tty_struct *);
        void    (*close)(struct tty_struct *);
        void    (*flush_buffer)(struct tty_struct *tty);
        ssize_t (*read)(struct tty_struct *tty, struct file *file,
                        unsigned char __user *buf, size_t nr);
        ssize_t (*write)(struct tty_struct *tty, struct file *file,
                         const unsigned char *buf, size_t nr);
        int     (*ioctl)(struct tty_struct *tty, struct file *file,
                         unsigned int cmd, unsigned long arg);
        int     (*compat_ioctl)(struct tty_struct *tty, struct file *file,
                                unsigned int cmd, unsigned long arg);
        void    (*set_termios)(struct tty_struct *tty, struct ktermios *old);
        __poll_t (*poll)(struct tty_struct *, struct file *,
                             struct poll_table_struct *);
        int     (*hangup)(struct tty_struct *tty);

        /*
         * The following routines are called from below.
         */
        void    (*receive_buf)(struct tty_struct *, const unsigned char *cp,
                               char *fp, int count);
        void    (*write_wakeup)(struct tty_struct *);
        void    (*dcd_change)(struct tty_struct *, unsigned int);
        int     (*receive_buf2)(struct tty_struct *, const unsigned char *cp,
                                char *fp, int count);

        struct  module *owner;

        int refcount;
};
复制代码

3.6 关系图

以tty串口设备为例,下图是tty_driver、tty_struct、tty_operations、tty_ldisc、tty_ldisc_ops之间的关系图:

 四、tty驱动API

linux内核提供了一组函数用于操作tty_driver结构体及tty设备。

4.1 注册tty设备

tty_register_device用于注册tty设备,函数定义在drivers/tty/tty_io.c:

复制代码
/**
 *      tty_register_device - register a tty device
 *      @driver: the tty driver that describes the tty device
 *      @index: the index in the tty driver for this tty device
 *      @device: a struct device that is associated with this tty device.
 *              This field is optional, if there is no known struct device
 *              for this tty device it can be set to NULL safely.
 *
 *      Returns a pointer to the struct device for this tty device
 *      (or ERR_PTR(-EFOO) on error).
 *
 *      This call is required to be made to register an individual tty device
 *      if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set.  If
 *      that bit is not set, this function should not be called by a tty
 *      driver.
 *
 *      Locking: ??
 */

struct device *tty_register_device(struct tty_driver *driver, unsigned index,
                                   struct device *device)   // 传入NULL
{
        return tty_register_device_attr(driver, index, device, NULL, NULL);
}

/**
 *      tty_register_device_attr - register a tty device
 *      @driver: the tty driver that describes the tty device
 *      @index: the index in the tty driver for this tty device
 *      @device: a struct device that is associated with this tty device.
 *              This field is optional, if there is no known struct device
 *              for this tty device it can be set to NULL safely.
 *      @drvdata: Driver data to be set to device.
 *      @attr_grp: Attribute group to be set on device.
 *
 *      Returns a pointer to the struct device for this tty device
 *      (or ERR_PTR(-EFOO) on error).
 *
 *      This call is required to be made to register an individual tty device
 *      if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set.  If
 *      that bit is not set, this function should not be called by a tty
 *      driver.
 *
 *      Locking: ??
 */
struct device *tty_register_device_attr(struct tty_driver *driver,
                                   unsigned index, struct device *device,
                                   void *drvdata,
                                   const struct attribute_group **attr_grp)
{
        char name[64];
        dev_t devt = MKDEV(driver->major, driver->minor_start) + index;   // 设备号
        struct ktermios *tp;
        struct device *dev;
        int retval;

        if (index >= driver->num) {        // 判断索引号
                pr_err("%s: Attempt to register invalid tty line number (%d)\n",
                       driver->name, index);
                return ERR_PTR(-EINVAL);
        }

        if (driver->type == TTY_DRIVER_TYPE_PTY)   // 设备类型 伪终端
                pty_line_name(driver, index, name);
        else
                tty_line_name(driver, index, name);  // 设置name为 driver->name+index

        dev = kzalloc(sizeof(*dev), GFP_KERNEL);  // 动态申请struct device
        if (!dev)
                return ERR_PTR(-ENOMEM);

        dev->devt = devt;               // 初始化设备号
        dev->class = tty_class;         // 类tty_class名称为tty
        dev->parent = device;
        dev->release = tty_device_create_release;
        dev_set_name(dev, "%s", name);  // 设置dev->init_name为driver->name+index
        dev->groups = attr_grp;
        dev_set_drvdata(dev, drvdata);   // 设置dev->p->driver_data=drvdata

        dev_set_uevent_suppress(dev, 1);

        retval = device_register(dev);   // 注册设备 会在/sys/class/tty类文件下创建名字为driver->name+index的文件夹
        if (retval)
                goto err_put;

        if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {  // 如果未指定TTY_DRIVER_DYNAMIC_ALLOC
                /*
                 * Free any saved termios data so that the termios state is
                 * reset when reusing a minor number.
                 */
                tp = driver->termios[index];   // 释放driver->termios[index]
                if (tp) {
                        driver->termios[index] = NULL;
                        kfree(tp);
                }

                retval = tty_cdev_add(driver, devt, index, 1);  // 注册1个字符设备,主设备号为driver->major,次设备号数为driver->minor_start + index
                if (retval)
                        goto err_del;
        }

        dev_set_uevent_suppress(dev, 0);
        kobject_uevent(&dev->kobj, KOBJ_ADD);

        return dev;

err_del:
        device_del(dev);
err_put:
        put_device(dev);

        return ERR_PTR(retval);
}
复制代码

仅有tty_driver是不够的,驱动必须依附于设备,tty_register_device函数用于注册关联于tty_driver的设备,index为设备的索引(范围是0~driver->num)。

该函数主要流程如下:

  • 动态申请struct device,并初始化初始化成员:
    • 初始化设备编号devt:主设备号为driver->major,次设备号为driver->minor_start+index;
    • 初始化class为tty_class,类名称为tty;
    • 初始化parent为device;
    • 初始化release为tty_device_create_release;
    • 初始化init_name为为driver->name+index;
    • 初始化groups为attr_grp;
    • 设置dev->p->driver_data为drvdata;
  • 调用device_register注册设备,会在/sys/class/tty类文件下创建名字为driver->name+index的文件夹;
  • 如果未指定TTY_DRIVER_DYNAMIC_ALLOC,则调用tty_cdev_add注册字符设备:
    • 调用cdev_alloc态分配字符设备,并赋值给driver->cdevs[xxx],主设备号为driver->major,次设备号数为driver->minor_start + index;文件操作集为tty_fops;
    • 调用cdev_add将字符设备添加到内核;

4.2 注销tty设备

tty_unregister_device用于卸载tty设备,函数定义在drivers/tty/tty_io.c:

复制代码
/**
 *      tty_unregister_device - unregister a tty device
 *      @driver: the tty driver that describes the tty device
 *      @index: the index in the tty driver for this tty device
 *
 *      If a tty device is registered with a call to tty_register_device() then
 *      this function must be called when the tty device is gone.
 *
 *      Locking: ??
 */

void tty_unregister_device(struct tty_driver *driver, unsigned index)
{
        device_destroy(tty_class,
                MKDEV(driver->major, driver->minor_start) + index);
        if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
                cdev_del(driver->cdevs[index]);
                driver->cdevs[index] = NULL;
        }
}
复制代码

4.3 分配tty驱动

alloc_tty_driver用于分配tty驱动,函数定义在include/linux/tty_driver.h:

复制代码
/*
 * DEPRECATED Do not use this in new code, use tty_alloc_driver instead.
 * (And change the return value checks.)
 */
static inline struct tty_driver *alloc_tty_driver(unsigned int lines)  // 
{
        struct tty_driver *ret = tty_alloc_driver(lines, 0);
        if (IS_ERR(ret))
                return NULL;
        return ret;
}

/* Use TTY_DRIVER_* flags below */
#define tty_alloc_driver(lines, flags) \
                __tty_alloc_driver(lines, THIS_MODULE, flags)
复制代码

这个函数返回tty_driver指针,其参数为要分配的设备数量,lines会被赋值给tty_driver的num成员。

4.3.1 __ tty_alloc_driver

__tty_alloc_driver函数定义在drivers/tty/tty_io.c,第一个参数为申请的tty字符设备数量,比如,某某芯片的介绍:”多达3个UART接口“,那么这个lines就是3;

复制代码
/**
 * __tty_alloc_driver -- allocate tty driver
 * @lines: count of lines this driver can handle at most
 * @owner: module which is responsible for this driver
 * @flags: some of TTY_DRIVER_* flags, will be set in driver->flags
 *
 * This should not be called directly, some of the provided macros should be
 * used instead. Use IS_ERR and friends on @retval.
 */
struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,
                unsigned long flags)
{
        struct tty_driver *driver;
        unsigned int cdevs = 1;
        int err;

        if (!lines || (flags & TTY_DRIVER_UNNUMBERED_NODE && lines > 1))
                return ERR_PTR(-EINVAL);

        driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);  // 动态申请内存
        if (!driver)
                return ERR_PTR(-ENOMEM);

        kref_init(&driver->kref);
        driver->magic = TTY_DRIVER_MAGIC;
        driver->num = lines;  // tty字符设备数量
        driver->owner = owner;
        driver->flags = flags;

        if (!(flags & TTY_DRIVER_DEVPTS_MEM)) {   // 未指定pts mem标志
                driver->ttys = kcalloc(lines, sizeof(*driver->ttys),   // 初始化指针数组,数组长度为lines,每个元素都是struct tty_struct *
                                GFP_KERNEL);
                driver->termios = kcalloc(lines, sizeof(*driver->termios),  // 初始化指针数组,数组长度为lines,每个元素都是struct ktermios*
                                GFP_KERNEL);
                if (!driver->ttys || !driver->termios) {
                        err = -ENOMEM;
                        goto err_free_all;
                }
        }

        if (!(flags & TTY_DRIVER_DYNAMIC_ALLOC)) {  // 未自定该标志
                driver->ports = kcalloc(lines, sizeof(*driver->ports),  // 初始化指针数组,数组长度为lines,每个元素都是struct tty_port*
                                GFP_KERNEL);
                if (!driver->ports) {
                        err = -ENOMEM;
                        goto err_free_all;
                }
                cdevs = lines;
        }

        driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL); // 初始化指针数组,数组长度为lines,每个元素都是struct cdev*
        if (!driver->cdevs) {
                err = -ENOMEM;
                goto err_free_all;
        }

        return driver;
err_free_all:
        kfree(driver->ports);
        kfree(driver->ttys);
        kfree(driver->termios);
        kfree(driver->cdevs);
        kfree(driver);
        return ERR_PTR(err);
}
复制代码

该函数主要就是进行一些初始化工作:

  • 动态分配tty_driver;
  • 初始化tty_driver成员:
    • 设置成员num;
    • 初始化成员cdevs;
    • 初始化成员ttys;
    • 初始化成员ports;
    • 初始化成员termios;
    • 等等;

4.4 注册tty驱动

tty_register_driver用于注册tty驱动,函数定义在drivers/tty/tty_io.c:

复制代码
/*
 * Called by a tty driver to register itself.
 */
int tty_register_driver(struct tty_driver *driver)
{
        int error;
        int i;
        dev_t dev;
        struct device *d;

        if (!driver->major) {     // 如果没有指定主设备号,动则态申请
                error = alloc_chrdev_region(&dev, driver->minor_start,
                                                driver->num, driver->name);
                if (!error) {
                        driver->major = MAJOR(dev);
                        driver->minor_start = MINOR(dev);
                }
        } else {
                dev = MKDEV(driver->major, driver->minor_start);
                error = register_chrdev_region(dev, driver->num, driver->name);
        }
        if (error < 0)
                goto err;

        if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {  // 指定动态分配
                error = tty_cdev_add(driver, dev, 0, driver->num);  // 注册driver->num个字符设备,相同的主设备号
                if (error)
                        goto err_unreg_char;
        }

        mutex_lock(&tty_mutex);        // 获取互斥锁
        list_add(&driver->tty_drivers, &tty_drivers);   // 添加到双向链表中
        mutex_unlock(&tty_mutex);      // 释放互斥锁

        if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {  // 未指定TTY_DRIVER_DYNAMIC_DEV
                for (i = 0; i < driver->num; i++) {
                        d = tty_register_device(driver, i, NULL);  // 注册tty设备
                        if (IS_ERR(d)) {
                                error = PTR_ERR(d);
                                goto err_unreg_devs;
                        }
                }
        }
        proc_tty_register_driver(driver);  // 向proc文件系统注册driver
        driver->flags |= TTY_DRIVER_INSTALLED;
        return 0;

err_unreg_devs:
        for (i--; i >= 0; i--)
                tty_unregister_device(driver, i);

        mutex_lock(&tty_mutex);
        list_del(&driver->tty_drivers);
        mutex_unlock(&tty_mutex);

err_unreg_char:
        unregister_chrdev_region(dev, driver->num);
err:
        return error;
}
复制代码

注册tty驱动成功时返回0,参数为由alloc_tty_driver 分配的tty_driver结构体指针。

tty_register_driver注册过程主要做了以下事情:

  • 申请设备号;
  • 如果指定了标志位TTY_DRIVER_DYNAMIC_ALLOC,则调用tty_cdev_add注册driver->num个字符设备,相同的主设备号;
    • 调用cdev_alloc态分配字符设备,并赋值给driver->cdevs[xxx],主设备号为driver->major,次设备号数为driver->minor_start + index;文件操作集为tty_fops;
    • 调用cdev_add将字符设备添加到内核;
  • 将当前tty_driver添加到全局双向链表tty_drivers;
  • 如果未指定TTY_DRIVER_DYNAMIC_DEV,调用tty_register_device注册tty设备,一共注册driver->num个struct device设备:
    • 设备class为tty_calss(名称为tty) ;
    • 设备名称为driver->name+编号,会在文件系统下创建设备节点文件/dev/设备名称;
  • 调用proc_tty_register_driver向 proc 文件系统添加driver ;
4.4.1 tty_cdev_add

tty_cdev_add函数主要就是动态分配struct cdev,并初始化ops为tty_fops;owner为driver->owner,同时将其添加到内核:

复制代码
static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
                unsigned int index, unsigned int count)
{
        int err;

        /* init here, since reused cdevs cause crashes */
        driver->cdevs[index] = cdev_alloc();             // 动态分配字符设备
        if (!driver->cdevs[index])
                return -ENOMEM;
        driver->cdevs[index]->ops = &tty_fops;          // 字符设备文件操作集
        driver->cdevs[index]->owner = driver->owner;
        err = cdev_add(driver->cdevs[index], dev, count);  // 添加字符设备到内核 第二个参数起始设备号  第三个参数为次设备数量
        if (err)
                kobject_put(&driver->cdevs[index]->kobj);
        return err;
}
复制代码

函数第一个参数为tty_driver,第二个参数为起始设备编号,第三个参数为drivers->cdevs元素索引,第四个参数为次设备数量。

这里我们需要留意的地方是字符设备的文件操作集被初始化为了tty_fops,比如当我们对字符设备/dev/ttySn进行读写的时候实际上调用的就是操作集中的方法。

4.4.2 tty_fops

tty_fops定义在drivers/tty/tty_io.c:

复制代码
static const struct file_operations tty_fops = {
        .llseek         = no_llseek,
        .read           = tty_read,
        .write          = tty_write,
        .poll           = tty_poll,
        .unlocked_ioctl = tty_ioctl,
        .compat_ioctl   = tty_compat_ioctl,
        .open           = tty_open,
        .release        = tty_release,
        .fasync         = tty_fasync,
        .show_fdinfo    = tty_show_fdinfo,
};
复制代码

用户空间的任何操作,首先对接到的是这个地方,也就是文件 tty_io.c中的这个操作集。

4.4.3 proc_tty_register_driver

函数最后调用proc_tty_register_driver向proc文件系统添加driver ;定义在fs/proc/proc_tty.c文件:

复制代码
/*
 * This function is called by tty_register_driver() to handle
 * registering the driver's /proc handler into /proc/tty/driver/<foo>
 */
void proc_tty_register_driver(struct tty_driver *driver)
{
        struct proc_dir_entry *ent;

        if (!driver->driver_name || driver->proc_entry ||
            !driver->ops->proc_show)
                return;

        ent = proc_create_single_data(driver->driver_name, 0, proc_tty_driver,
                               driver->ops->proc_show, driver);
        driver->proc_entry = ent;
}
复制代码

4.5 注销tty驱动

tty_unregister_driver用于注销tty驱动,函数定义在drivers/tty/tty_io.c:

复制代码
/*
 * Called by a tty driver to unregister itself.
 */
int tty_unregister_driver(struct tty_driver *driver)
{
#if 0
        /* FIXME */
        if (driver->refcount)
                return -EBUSY;
#endif
        unregister_chrdev_region(MKDEV(driver->major, driver->minor_start),
                                driver->num);
        mutex_lock(&tty_mutex);
        list_del(&driver->tty_drivers);
        mutex_unlock(&tty_mutex);
        return 0;
}
复制代码

4.6 设置tty驱动操作

tty_set_operations函数用于设置tty驱动操作,定义在drivers/tty/tty_io.c:

void tty_set_operations(struct tty_driver *driver,
                        const struct tty_operations *op)
{
        driver->ops = op;
};

函数会将tty_operations结构体中的函数指针拷贝给tty_driver对应的函数指针,在具体的tty驱动中,通常会定义1个设备特定的 tty_operations。

五、tty_open

我们在调用tty_register_driver进行tty_driver注册的时候,该函数内部会调用tty_register_device注册tty字符设备,并且将tty字符设备文件操作集设置为tty_fops。

这一小节,我们将对tty_fops文件操作集进行分析。tty_fops定义在drivers/tty/tty_io.c:

复制代码
static const struct file_operations tty_fops = {
        .llseek         = no_llseek,
        .read           = tty_read,
        .write          = tty_write,
        .poll           = tty_poll,
        .unlocked_ioctl = tty_ioctl,
        .compat_ioctl   = tty_compat_ioctl,
        .open           = tty_open,
        .release        = tty_release,
        .fasync         = tty_fasync,
        .show_fdinfo    = tty_show_fdinfo,
};
复制代码

这里我们以open函数为例进行讲解。下图为tty_open函数调用过程,比较复杂,后面我们一一分析:

5.1 tty_open

当我们打开一个tty设备时,即对设备节点/dev/ttyN进行open操作的时候,tty_open函数会被调用:

复制代码
static int tty_open(struct inode *inode, struct file *filp)
{
        struct tty_struct *tty;
        int noctty, retval;
        dev_t device = inode->i_rdev;
        unsigned saved_flags = filp->f_flags;

        nonseekable_open(inode, filp);

retry_open:
        retval = tty_alloc_file(filp);
        if (retval)
                return -ENOMEM;

        tty = tty_open_current_tty(device, filp);  // 获取当前进程对应控制终端所对应的tty_struct指针
        if (!tty)
                tty = tty_open_by_driver(device, inode, filp);  //进入这里  获取当前设备对应的tty_struct 

        if (IS_ERR(tty)) {
                tty_free_file(filp);
                retval = PTR_ERR(tty);
                if (retval != -EAGAIN || signal_pending(current))
                        return retval;
                schedule();
                goto retry_open;
        }

        tty_add_file(tty, filp);

        check_tty_count(tty, __func__);
        tty_debug_hangup(tty, "opening (count=%d)\n", tty->count);

        if (tty->ops->open)
                retval = tty->ops->open(tty, filp);   // 重点
        else
                retval = -ENODEV;
        filp->f_flags = saved_flags;

        if (retval) {
                tty_debug_hangup(tty, "open error %d, releasing\n", retval);

                tty_unlock(tty); /* need to call tty_release without BTM */
                tty_release(inode, filp);
                if (retval != -ERESTARTSYS)
                        return retval;

                if (signal_pending(current))
                        return retval;

                schedule();
                /*
                 * Need to reset f_op in case a hangup happened.
                 */
                if (tty_hung_up_p(filp))
                        filp->f_op = &tty_fops;
                goto retry_open;
        }
        clear_bit(TTY_HUPPED, &tty->flags);

        noctty = (filp->f_flags & O_NOCTTY) ||
                 (IS_ENABLED(CONFIG_VT) && device == MKDEV(TTY_MAJOR, 0)) ||
                 device == MKDEV(TTYAUX_MAJOR, 1) ||
                 (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
                  tty->driver->subtype == PTY_TYPE_MASTER);
        if (!noctty)
                tty_open_proc_set_tty(filp, tty);
        tty_unlock(tty);
        return 0;
}
复制代码

针对tty_open接口而言,主要实现如下几个功能:

  • 若打开的tty设备为控制终端,则通过调用tty_open_current_tty获取当前进程对应控制终端所对应的tty_struct指针;
  • 若1中没有找到对应tty_struct,则调用tty_open_by_driver根据字符设备号从tty_drivers链表中查找已注册的tty_driver,若该tty_driver与对应tty端口的tty_struct已完成绑定,则获取对应的tty_struct指针;
  • 若以上两步均没有获取到tty端口对应的tty_struct,则说明该tty端口对应的tty_struct还没有创建,则调用tty_init_dev完成tty_struct的创建,并完成tty_struct与tty_driver的绑定、tty_struct与tty_port、tty_struct与ldisc、tty_struct与tty device的绑定操作, 并调用tty_ldisc_setup,进行线路规程的打开(如termios的设置,ldisc的使能、ldisc缓存的初始化等)等等;
  • 调用tty->ops->open函数;
5.1.1 tty_open_current_tty

tty_open_current_tty函数用于获取当前进程对应控制终端所对应的tty_struct指针;

复制代码
/**
 *      tty_open_current_tty - get locked tty of current task
 *      @device: device number
 *      @filp: file pointer to tty
 *      @return: locked tty of the current task iff @device is /dev/tty
 *
 *      Performs a re-open of the current task's controlling tty.
 *
 *      We cannot return driver and index like for the other nodes because
 *      devpts will not work then. It expects inodes to be from devpts FS.
 */
static struct tty_struct *tty_open_current_tty(dev_t device, struct file *filp)
{
        struct tty_struct *tty;
        int retval;

        if (device != MKDEV(TTYAUX_MAJOR, 0))  // 主设备号不等于TTYAUX_MAJOR,直接返回   实际上走这里
                return NULL;

        tty = get_current_tty();  // 返回当前进程的控制终端
        if (!tty)
                return ERR_PTR(-ENXIO);

        filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
        /* noctty = 1; */
        tty_lock(tty);
        tty_kref_put(tty);      /* safe to drop the kref now */

        retval = tty_reopen(tty);
        if (retval < 0) {
                tty_unlock(tty);
                tty = ERR_PTR(retval);
        }
        return tty;
}
复制代码

如果主设备号不等于TTYAUX_MAJOR,函数直接返回0;tty_reopen函数内部调用了tty_ldisc_reinit。

5.1.2 tty_open_by_driver

tty_open_by_driver函数根据字符设备号从tty_drivers链表中查找已注册的tty_driver,若该tty_driver与对应tty端口的tty_struct已完成绑定,则获取对应的tty_struct指针;

若没有获取到tty端口对应的tty_struct,则说明该tty端口对应的tty_struct还没有创建,则调用tty_init_dev完成tty_struct的创建,并完成tty_struct与tty_driver的绑定、tty_struct与tty_port、tty_struct与ldisc、tty_struct与tty device的绑定操作, 并调用tty_ldisc_setup,进行线路规程的打开(如termios的设置,ldisc的使能、ldisc缓存的初始化等)等等;

复制代码
/**
 *      tty_open_by_driver      -       open a tty device
 *      @device: dev_t of device to open
 *      @inode: inode of device file
 *      @filp: file pointer to tty
 *
 *      Performs the driver lookup, checks for a reopen, or otherwise
 *      performs the first-time tty initialization.
 *
 *      Returns the locked initialized or re-opened &tty_struct
 *
 *      Claims the global tty_mutex to serialize:
 *        - concurrent first-time tty initialization
 *        - concurrent tty driver removal w/ lookup
 *        - concurrent tty removal from driver table
 */
static struct tty_struct *tty_open_by_driver(dev_t device, struct inode *inode,
                                             struct file *filp)
{
        struct tty_struct *tty;
        struct tty_driver *driver = NULL;
        int index = -1;
        int retval;

        mutex_lock(&tty_mutex);
        driver = tty_lookup_driver(device, filp, &index);  // 根据设备号从tty_drivers链表中查找已注册的tty_driver,由于已经注册了tty_driver,所以这个能找到
        if (IS_ERR(driver)) {
                mutex_unlock(&tty_mutex);
                return ERR_CAST(driver);
        }

        /* check whether we're reopening an existing tty */
        tty = tty_driver_lookup_tty(driver, filp, index);  // 返回driver->ttys[idx];
因为tty_init_dev函数在这之前还未被调用,而且目前为止未发现其他位置初始化tty_struct并放入ttys[],所以这里返回NULL 
if (IS_ERR(tty)) { // 返回NULL mutex_unlock(&tty_mutex); goto out; } if (tty) { if (tty_port_kopened(tty->port)) { tty_kref_put(tty); mutex_unlock(&tty_mutex); tty = ERR_PTR(-EBUSY); goto out; } mutex_unlock(&tty_mutex); retval = tty_lock_interruptible(tty); tty_kref_put(tty); /* drop kref from tty_driver_lookup_tty() */ if (retval) { if (retval == -EINTR) retval = -ERESTARTSYS; tty = ERR_PTR(retval); goto out; } retval = tty_reopen(tty); if (retval < 0) { tty_unlock(tty); tty = ERR_PTR(retval); } } else { /* Returns with the tty_lock held for now */ tty = tty_init_dev(driver, index); // 执行这个 分配一个tty设备,即strucr tty_struct,并进行初始化各个成员 mutex_unlock(&tty_mutex); } out: tty_driver_kref_put(driver); return tty; }
复制代码

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(561)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示