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-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
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-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了