【摘要】本文详细解读了linux系统下的tty子系统的深层次原理和架构,并参考了LDD3中的代码实例讲述了无硬件下实现一个简单的tty设备驱动模块的编写。对了解tty子系统及下部串口驱动模块的学习有较好的参考价值。

1、tty设备简介

tty一词源于Teletypes,或Teletypewriters,它是最早出现的一种终端设备,类似电传打字机,由Teletype公司生产。最初tty是指连接到Unix系统上的物理或者虚拟终端。终端是一种字符型设备,通常使用tty来统称各种类型的终端设备。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备。

它还有多种类,例如串口(ttySn、ttySACn、ttyOn)、USB到串口的转换器(ttyUSBn),还有需要特殊处理才能正常工作的调制解调器(比如传统的WinModem类设备)等。tty虚拟设备支持虚拟控制台,它能通过键盘及网络连接或者通过xterm会话登录到计算机上。

其实起初终端和控制台都不是个人电脑的概念,而是多人共用的小型中型大型计算机上的概念。终端为主机提供了人机接口,每个人都通过终端使用主机的资源。终端有字符终端和图形终端两种。一台主机可以连很多终端。控制台是一种特殊的人机接口, 是人控制主机的第一人机接口。

而主机对于控制台的信任度高于其他终端。对此还可以结合内核启动代码中init进程打开/dev/console和执行两次sys_dup(0),以及标准输入、标准输出、标准出错,还有就是进程fork后的标准输入输出的复制情况来一起理解。而个人计算机只有控制台,没有终端。当然愿意的话,可以在串口上连一两台字符哑终端。

但是linux按POSIX标准把个人计算机当成小型机来用,在控制台上通过getty软件虚拟了六个字符哑终端(或者叫虚拟控制台终端tty1-tty6)(数量可以在/etc/inittab里自己调整)和一个图型终端,在虚拟图形终端中又可以通过软件(如rxvt)再虚拟无限多个伪终端(pts/0等)

但这全是虚拟的,虽然用起来一样,但实际上没有物理实体。所以在个人计算机上,只有一个实际的控制台,没有终端,所有终端都是在控制台上用软件模拟的。要把个人计算机当主机再通过串口或网卡外连真正的物理终端也可以,论成本,谁又会怎么做呢。

终端按照其自身能力分类,可以分为:

  1. 哑终端(瘦客户端)

早期的计算机终端是通过串行RS-232通信的,它只能解释有限数量的控制码(CR,LF等),但没有能力处理执行特殊的转义序列功能(如清行、清屏或控制光标的位置)。简单来说就是处理能力有限的终端机,他们一般基本上只具有和机械电传打字机类似的有限功能。这种类型的终端称为哑终端。

现在仍然在现代类Unix系统上得到支持,通过设置环境变量TERM=dumb。哑终端有时用来指任何类型的通过RS-232连接的传统计算机终端,不对数据进行本地处理或本地执行用户程序的串行通信终端。哑终端有时也指功能有限,只有单色文本处理能力或直接传输每一个键入的字符而不等待主机轮询的公共计算机终端。

  1. 智能终端(胖客户端)

智能终端就是有能力处理转义序列,也就是说处理能力较强的终端机。


Linux系统的终端设备一般分为控制台、伪终端pty、串口终端(/dev/ttySn)和其它类型4种。

1.控制台

1.1 系统控制台(/dev/console)

/dev/console是系统控制台,是与操作系统交互的设备。系统所产生的信息会发送到该设备上。平时我们看到的PC只有一个屏幕和键盘,它其实就是控制台。目前只有在单用户模式下,才允许用户登录控制台/dev/console。(可以在单用户模式下输入tty命令进行确认)。

console有缓冲的概念,为内核提供打印输出。内核把要打印的内容装入缓冲区__log_buff,然后由console来决定打印到哪里(比如是tty0还是ttySn等)。console指向激活的终端。历史上,console指主机本身的屏幕和键盘,而tty指用电缆链接的其它位置的控制台。

某些情况下console和tty0是一致的,就是当前所使用的是虚拟终端,也是激活虚拟终端。所以有些资料中称/dev/console是到/dev/tty0的符号链接,但是这样说现在看来是不对的:根据内核文档,在2.1.71之前,/dev/console根据不同系统设定,符号链接到/dev/tty0或者其他tty*上,在2.1.71版本之后则完全由内核代码内部控制它的映射。

如果一个终端设备要实现console功能,必须向内核注册一个struct console结构,一般的串口驱动中都会有。如果设备要实现tty功能,必须要向内核的tty子系统注册一个struct tty_driver结构,注册函数在drivers/tty/tty_io.c中。一个设备可以同时实现console和tty_driver,一般串口都这么做。

1.2 当前控制台(/dev/tty)

这是应用程序中的概念,如果当前进程有控制终端(Controlling Terminal),那么/dev/tty就是当前进程控制台的设备文件。对于你登录的shell,/dev/tty就是你使用的控制台,设备号是(5,0)。不过它并不指任何物理意义上的控制台,/dev/tty会映射到当前设备(使用命令tty可以查看它具体对应哪个实际物理控制台设备)。输出到/dev/tty的内容只会显示在当前工作终端上(无论是登录在ttyn中还是pty中)。

你如果在控制台界面下(即字符界面下)那么dev/tty就是映射到dev/tty1-6之间的一个(取决于你当前的控制台号),但是如果你现在是在图形界面(Xwindows),那么你会发现现在的/dev/tty映射到的是/dev/pts的伪终端上。/dev/tty有些类似于到实际所使用终端设备的一个联接

你可以输入命令tty,显示当前映射终端如:/dev/tty1或者/dev/pts/0等。也可以使用命令ps -ax来查看其他进程与哪个控制终端相连。

在当前终端中输入 echo “tekkaman” > /dev/tty ,都会直接显示在当前的终端中。

1.3 虚拟控制台 (/dev/ttyn)

/dev/ttyn是进程虚拟控制台,他们共享同一个真实的物理控制台。如果在进程里打开一个这样的文件且该文件不是其他进程的控制台时,那该文件就是这个进程的控制台。

进程printf数据会输出到这里。在PC上,用户可以使用alt+Fn切换控制台,现在不知道怎么回事我用Ctrl + Alt + Fn才能切换。这没具体看过为啥。可能是Linux没有继承UNIX这方面的传统罢了。看起来感觉存在多个屏幕,这种虚拟控制台对应tty1~n,其中:/dev/tty1代表第1个虚拟控制台;当使用ALT+F2进行切换时,系统的虚拟控制台为/dev/tty2 ,当前控制台(/dev/tty)则指向/dev/tty2

在UNIX系统中,计算机显示器通常被称为控制台(console)。它仿真了类型为Linux的一种终端,并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等。当你在控制台上登录时,使用的是tty1。使用Alt+[F1—F6]组合键时,我们就可以切换到tty2、tty3等上面去。

读者可以登录到不同的虚拟控制台上去,因而可以让系统同时有几个不同的会话存在。

比较特殊的是/dev/tty0,他代表当前虚拟控制台,其实就是当前所使用虚拟控制台的一个别名。因此不管当前正在使用哪个虚拟控制台(注意:这里是虚拟控制台,不包括伪终端),系统信息都会重定位到/dev/tty0上。

只有系统或超级用户root可以向/dev/tty0进行写操作。tty0是系统自动打开的,但不用于用户登录。在Framebuffer设备没有启用的系统中,可以使用/dev/tty0访问显卡。

2. 伪终端pty

伪终端(Pseudo Terminal)是终端的发展,为满足现在需求(比如网络登陆、xwindow窗口的管理)。它是成对出现的逻辑终端设备(即master和slave设备, 对master的操作会反映到slave上)。它多用于模拟终端程序,是远程登陆(telnet、ssh、xterm等)后创建的控制台设备。

简单说主终端和类似sshd,telnetd等用户空间的远程协议处理进程连接,而从终端则和shell之类的实际进程连接。在处理远程登录的时候,一般都是由远程协议处理进程打开主终端和从终端,然后就在远程网络终端和本机shell之间建立了一条双向通道(远程网络终端(套接字)<—>本机协议处理进程<—>主终端<—>从终端<—>shell)。

在这个“打开主从终端建立连接”的语义以及其实现上,有着不同的标准,总的来说有三种方式,分别是SVR4的方式,BSD的方式以及linux的方式。

在“建立连接”的语义上SVR4的方式使用“流”来建立这条连接,而BSD和linux则是自动建立的。

在“打开主从终端”的语义上,SVR4和linux是自动确定主终端并打开主终端后自动确定从终端,而BSD则必须手工确定和打开主终端。

可见linux处理伪终端的方式是结合SVR4和BSD两种UNIX标准的结果,linux不仅实现这种有意义的最佳组合,而且分别实现了SRV和BSD的两种方式的接口,如果编译CONFIG_LEGACY_PTYS宏,则可以使用BSD的方式,如果编译CONFIG_UNIX98_PTYS,则实现SRV4的接口。

  • BSD接口:较简单,master为/dev/pty [p-za-e] [0-9a-f];slave为 /dev/tty [p-za-e] [0-9a-f] ,它们都是配对的出现的。例如/dev/ptyp3和/dev/ttyp3。但由于在编程时要找到一个合适的终端需要逐个尝试,所以逐渐被放弃。
  • Unix 98接口(SRV4):仅使用一个**/dev/ptmx作为master设备,任何sshd,telnetd之类的进程都可以只使用这一个终端设备文件,在每次打开操作时会得到一个master设备fd,并在/dev/pts/目录下得到一个slave设备(如 /dev/pts/3和/dev/ptmx)**,这样就避免了逐个尝试的麻烦。在ptmx_open中,不仅系统可以自动分配一个主终端,而且还为该主终端绑定了一个从终端,主终端设置到file结构体的private_data字段上,之后诸如sshd,telnetd之类的进程读写/dev/ptmx文件时,虽然它们读写的是同一个文件,可是由于file结构体不再它们之间共享,因此它们取到的file->private_data也就不同了

由于可能有好几千个用户登陆,所以/dev/pts/* 是动态生成的,不象其他设备文件是构建系统时就已经产生的硬盘节点(如果未使用devfs、udev、mdev等) 。**第一个用户登陆,设备文件为/dev/pts/0,第二个为/dev/pts/1,以此类推。**它们并不与实际物理设备直接相关。现在大多数系统是通过此接口实现pty。

我们在X Window下打开的终端或使用telnet或ssh等方式登录Linux主机,此时均通过pty设备。例如,如果某人在网上使用telnet程序连接到你的计算机上,则telnet程序就可能会打开/dev/ptmx设备获取一个fd。此时一个getty程序就应该运行在对应的/dev/pts/* 上。当telnet从远端获取了一个字符时,该字符就会通过ptmx、pts/* 传递给 getty程序,而getty程序就会通过pts/* 、ptmx和telnet程序往网络上返回“login:”字符串信息。这样,登录程序与telnet程序就通过“伪终端”进行通信。

  • telnet<—>/dev/ptmx(master)<—>pts/*(slave)<—>getty

如果一个程序把 pts/* 看作是一个串行端口设备,则它对该端口的读/写操作会反映在该逻辑终端设备对的另一个/dev/ptmx上,而/dev/ptmx则是另一个程序用于读写操作的逻辑设备。这样,两个程序就可以通过这种逻辑设备进行互相交流,这很象是逻辑设备对之间的管道操作。对于pts/* ,任何设计成使用一个串行端口设备的程序都可以使用该逻辑设备。但对于使用/dev/ptmx的程序,则需要专门设计来使用/dev/ptmx逻辑设备。通过使用适当的软件,就可以把两个甚至多个伪终端设备连接到同一个物理串行端口上。

  • Tmux Server打开一对伪终端,自己持有主设备,将次设备继承给它Fork出来的Bash,此一对进程进入后台,不再归属任何终端。
  • 一旦Tmux Client运行于某个SSH终端,它会把当前终端的pts传递给Tmux Server,从而让Tmux Server作为一个数据代理传递输入和输出。
  • 一旦Tmux Client运行,它便成为了当前Bash的前台进程,通过重定向之后,当前Tmux Client便成为了Bash0的前端,接收Bash0的输入和输出。文件句柄转交后的流程如下:
    1. 有人在远端的Windows主机上敲入一个字符“a”;
    2. 字符“a”经由SSH客户端加密后传输到Linux SSH服务器SSHd并解密;
    3. 字符“a”通过SSHd的ptmx写入
    4. Tmux Server从pts/2将字符“a”读出并写入ptmx;
    5. Bash0将字符“a”从pts/1读出并执行;
    6. Bash0将-bash: a: command not found按原路返回给Windows。

3. 串口终端(/dev/ttySn)

串行端口终端(Serial PortTerminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。有段时间串行端口设备通常被称为终端设备,那时它的最大用途就是用来连接终端,所以这些串行端口所对应的设备名称是/dev/tts/0(或/dev/ttyS0)、/dev/tts/1(或/dev /ttyS1)等,设备号分别是(4,0)、(4,1)等(对应于win系统下的COM1、COM2等)。若要向一个端口发送数据,可以在命令行上把标准输出重定向到这些特殊文件名上即可。

我们可以在命令行提示符下键入:echo "tekkaman" > /dev/ttyS1会把“tekkaman”发送到连接在ttyS1(COM2)端口的设备上。

在2.6以后的内核后,一些三星的芯片将串口终端设备节点命名为ttySACn。TI的Omap系列芯片从2.6.37开始,芯片自带的UART设备开始使用专有的的omap-uart驱动,故设备节点命名为ttyOn,以区别于使用8250驱动时的设备名“ttySn”。这其中包括笔者用到的这个S3C2440。所以我们在Uboot启动参数中要设置console = ttySAC0才可以。这一句的意思其实就是把ttySAC0当做我们的控制台终端。

4. 其它类型

还针对很多不同的字符设备存在有很多其它种类的终端设备特殊文件,例如针对ISDN设备的**/dev/ttyIn**终端设备等。


2、tty子系统

在Linux kernel中,tty驱动不像于spi,iic等那么架构简单,它是一个庞大的系统,它的框架大体如下图一。我们作为普通的驱动开发移植人员,不会从零写tty驱动,一般都是厂家根据现有的tty驱动和自家芯片修改,拿到板子按照厂家的配置,串口应该使能直接使用的。但是开发的过程中也有可能需要用到串口,一般会修改serial驱动,这样我们不会动tty_core层。

Linux tty子系统包含:tty核心(tty_core),tty线路规程(tty_line_discipine)和tty驱动(tty_driver)。tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的硬件驱动。其整体框架如下图所示:

tty 核心从用户获取将要发送给 tty 设备的数据,接着传递它到 tty 线路规程驱动,再传递到 tty 驱动。 这个 tty 驱动转换数据为可以发送给硬件的格式。

从 tty 硬件收到的数据通过 tty 驱动向上回流,先进入 tty 线路规程驱动,再进入 tty 核心, 最后被用户获取。有时 tty 驱动直接和 tty 核心通讯,并且 tty 核心直接发送数据到 tty 驱动。但通常 tty 线路规程在它们 2 者之间修改数据格式以适配某种协议规则(蓝牙、PPP等)。

1. tty核心

tty 核心提供一个非常容易的方式给任何 tty 驱动来维护一个文件在/proc/tty/driver 目录中。如果驱动定义了read_proc 或者 write_proc 函数,这个文件被创建。接着, 任何在这个文件上的读或写调用被发送给这个驱动.
这些函数的格式如同标准的 /proc 文件处理函数:

/* 打印出当前注册的端口号的read_proc函数示例*/
static int tiny_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
{struct tiny_serial *tiny;off_t begin = 0;int length = 0;int i;length += sprintf(page, "tinyserinfo:1.0 driver:%s\n", DRIVER_VERSION);for (i = 0; i < TINY_TTY_MINORS && length < PAGE_SIZE; ++i) {tiny = tiny_table[i];if (tiny == NULL)continue;length += sprintf(page+length, "%d\n", i);if ((length + begin) > (off + count))goto done;if ((length + begin) < off) {begin += length;length = 0;}}*eof = 1;done:if (off >= (length + begin))return 0;*start = page + (off-begin);return (count < begin+length-off) ? count : begin + length-off;
}

当 tty 驱动被注册时, 或者当单个 tty 设备被创建时,tty 核心处理所有的 sysfs 目录和设备创建。 依赖在 struct tty_driver 中的TTY_DRIVER_NO_DEVFS 标志。单个目录一直包含 dev 文件, 它允许用户空间工具来决定分配给设备的主次号。

它还包含一个 device 和 driver 符号连接,如果一个指向有效的 struct device 的指针被传递给读 tty_register_device的调用。除了这 3 个文件, 对单个 tty 驱动不可能在这个位置创建新的sysfs 文件。这个会可能在将来的内核发行中改变。

2. tty线路规程

线路(行)规程规定了键盘,串口,打印机,显示器等输入输出设备和用户态Shell等程序之间的行为规范,键盘上的按键事件被行规程解释成了Shell可以理解的输入并给出相应的输出。人们要想操作计算机,这套规程是必不可少的,它事实上规定了**信息从外部进入计算机的规范**。

当我们在一个终端上按下按键L的时候,终端只是把字母L回显了回来,紧接着按下按键S,依然是回显字母S,随后我们按下回车键,回显的不再是回车键,而是列出并显示了当前目录下的所有文件,这些规则就是线路规程。

可以使用stty命令来展示你的终端行规程的配置。

下图显示了Linux下线路规程在整个体系结构中的位置:

  • SLIP

    • SLIP就是**Serial Line Internet Protocol(串行线路网际协议)**可能现在很少有人再使用它了,毕竟现如今都是以太网和光纤的天下了,谁还会用串口线来传输网络数据包。但是它在以前很长一段时间一直作为连接运行TCP/IP协议的主机的专用链路存在的。

    • 我们知道,TCP/IP是对等层通信协议,但是最终的数据包不得不通过某种物理介质传输,因此还需要一种纵向的协议才可以让对等层通信得以实现。我们把横向的对等层协议叫做**通信协议,而纵向的协议叫做传输协议,行规程事实上就是一种传输协议,SLIP实际上就是一种行规程**,SLIP行规程把串口线上传输的字符解释成IP数据报文并向上递送。这种行规程和TCP/IP的关系如下所示:

    • 可以看得出,SLIP作为一中行规程,并没有把解析后的数据继续提交到与之绑定的TTY缓冲区,而是将解析后的数据作为IP数据报直接递送给了TCP/IP协议栈来处理。

    • 广义地来讲,其实像以太网规范这种也可以叫做某种行规程,毕竟它也是约定IP层软件和传输介质之间行为规范的协议,起到的均是对数据格式化的作用,但由于如今超高速传输早就完完全全基于比特流序列,不再通过定义以字符为边界的块来封装数据,所以再叫做行规程就有点词不达意了,但本质上都是一样的,都是定义在某种介质上如何把数据包封装的协议。

  • 串口、键盘和显示器在tty系统中的层次

    • 当通过键盘在命令行输入“ls+回车”后屏幕显示结果的内在过程如下:

      1. keyborad 输入“l”,通过串口发送给驱动;
      2. 驱动发送给line discipline,并在line discipline中的buffer里储存起来。
      3. 然后通过line discipline返回给显示驱动,并在终端显示。
      4. keyborad 输入“s”,之后的过程同l
      5. 最后键盘输入回车,传递到line discipline层后,解析为把之前buffer中的字符串ls作为命令传给应用层的进程执行
      6. 用户进程(shell)将得到的命令结果返回给行规程(line discipline)。
      7. 行规程再返回给显示驱动,并在终端上显示。

应用程序通过open/read/write等标准接口访问硬件。引入行规程之后还要设置行规程,通过/ioctl或者其他封装好的函数去设置行规程。

3. tty驱动/tty设备

有 3 种不同类型 tty 驱动: 控制台串口, 和 pty。控制台和 pty 驱动已经被编写进内核,所以任何使用 tty 核
心来与用户和系统交互的需要我们自己编写新驱动的只有串口驱动。

通过查看/proc/tty/drivers文件,可找到当前被加载到内核中的有哪些类型的 tty 驱动和设备。缺省的串口驱动创建一个文件在这个目录中来展示许多串口特定的硬件信息:

驱动的名子 节点名子 主设备号 次设备号范围 驱动的类型

/dev/tty /dev/tty 5 0 system:/dev/tty
/dev/console /dev/console 5 1 system:console
/dev/ptmx /dev/ptmx 5 2 system
/dev/vc/0 /dev/vc/0 4 0 system:vtmaster
usbserial /dev/ttyUSB 188 0-254 serial
serial /dev/ttyS 4 64-67 serial
pty_slave /dev/pts 136 0-255 pty:slave
pty_master /dev/ptm 128 0-255 pty:master


通过查看/sys/class/tty文件,可找到所有当前注册到内核中的 tty 设备目录,目录下有一个 “dev” 文件包含有分配给那个 tty 设备的主次编号,和该驱动对应的设备及驱动的符号链接。

/sys/class/tty/
|-- console
| -- dev |-- ptmx | – dev
|-- tty
| -- dev |-- tty0 | – dev

|-- ttyUSB0
| |-- dev
| |-- device
-> …/…/…/devices/pci0000:00/0000:00:09.0/usb3/3-1/3-1:1.0/ttyUSB0
| `-- driver -> …/…/…/bus/usb-serial/drivers/keyspan_4

  • struct tty_driver定义

    struct tty_driver {int  magic;      //这个结构的"魔术"值. 应当一直设为 TTY_DRIVER_MAGICstruct cdev cdev;struct module   *owner;     //驱动的模块拥有者const char    *driver_name;   //驱动的名子, 用在 /proc/tty 和 sysfs.const char *name;          //驱动的节点名int name_base;              //当创建设备名子时,使用的起始数字int    major;                  //驱动的主编号int minor_start;    //驱动的开始次编号. 这常常设为 name_base 的相同值int minor_num;      /* number of *possible* devices */int   num;            /* number of devices allocated */short  type;       /* type of tty driver */short   subtype;    //其值依赖于typestruct ktermios init_termios; //当创建设备时的初始化 struct termios 值int   flags;      /* tty driver flags */int   refcount;   /* for loadable tty drivers */struct proc_dir_entry *proc_entry; /* /proc fs entry ,它由 tty 核心创建如果驱动实现了
    write_proc 或者 read_proc 函数 */struct tty_driver *other; /* 指向一个 tty 从驱动. 这只被 pty 驱动使用, 并且不应当被其他的tty 驱动使用 *//** Pointer to the tty data structures*/struct tty_struct **ttys;struct ktermios **termios;struct ktermios **termios_locked;void *driver_state;    /* tty 驱动的内部状态. 应当只被 pty 驱动使用 *//** Interface routines from the upper tty layer to the tty* driver. Will be replaced with struct tty_operations.*/int  (*open)(struct tty_struct * tty, struct file * filp);void (*close)(struct tty_struct * tty, struct file * filp);int  (*write)(struct tty_struct * tty, const unsigned char *buf, int count);void (*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, struct file * file,unsigned int cmd, unsigned long arg);long (*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);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);void (*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);/*在对/proc/tty/driver目录中的文件的读写回调函数*/int (*read_proc)(char *page, char **start, off_t off,int count, int *eof, void *data);int (*write_proc)(struct file *file, const char __user *buffer,unsigned long count, void *data);int (*tiocmget)(struct tty_struct *tty, struct file *file);int (*tiocmset)(struct tty_struct *tty, struct file *file,unsigned int set, unsigned int clear);struct list_head tty_drivers;
    };
    
    • owner:为了防止 tty 驱动在 tty 端口打开时被卸载,一般取值为THIS_MODULE

    • driver_name :驱动名,它出现在 /proc/tty/drivers 文件, 以及 /sys/class/tty 类目录下。

    • name:节点名,出现在/dev树中,通过在这个字串的后面追加在使用的 tty 设备号来创建多个 tty 设备。另外还在/sys/class/tty 目录中创建一个设备名子。如果 devfs 被使能,内核中的串口驱动设置这个 name 成员为 tts/ ,如果没有使能,则为ttyS/

      # 当driver_name = "tiny_tty"; name = "ttty"; devfs_name = "tts/ttty%d"; 时:
      $ cat /proc/tty/drivers
      tiny_tty /dev/ttty 240 0-3 serial$ tree /sys/class/tty/ttty*
      /sys/class/tty/ttty0
      `-- dev
      /sys/class/tty/ttty1
      `-- dev
      /sys/class/tty/ttty2
      `-- dev
      /sys/class/tty/ttty3
      `-- dev$ cat /sys/class/tty/ttty0/dev
      240:0
      
    • major 变量描述这个驱动的主编号是什么

    • type、subtype :声明这个驱动是什么类型:

      • TTY_DRIVER_TYPE_SYSTEM :仅由 tty 子系统内部使用的驱动(非"正常"的tty 驱动),其可能的子类型有

        • SYSTEM_TYPE_TTY
        • SYSTEM_TYEP_CONSOLE
        • SYSTEM_TYPE_SYSCONS
        • SYSTEM_TYPE_SYSPTMX
      • TTY_DRIVER_TYPE_CONSOLE :仅被控制台驱动使用
      • TTY_DRIVER_TYPE_SERIAL :串行类型接口驱动,其可能的子类型有
        • SERIAL_TYPE_NORMAL (最常使用)
        • SERIAL_TYPE_CALLOUT(现已淘汰)
      • TTY_DRIVER_TYPE_PTY :伪控制台接口驱动 ,其可能的子类型有
        • PTY_TYPE_MASTER
        • PTY_TYPE_SLAVE
    • flags:指示驱动的当前状态和它是什么类型 tty 驱动。你必须使用的位掩码宏为:

      • TTY_DRIVER_RESET_TERMIOS :表示当最后一个进程关闭tty设备时,自动复位termios 设置。
      • TTY_DRIVER_REAL_RAW :表示线路规程不必查看从tty 驱动收到的每个字符,但保证证发送奇偶或者坏字符通知给线路规程。因为它以一种更快的方式来处理接收到的字符,所以大部分tty驱动会设置该标识。
      • TTY_DRIVER_NO_DEVFS :表示当注册tty驱动时,tty 核心不创建任何devfs 条目给这个 tty 设备。这对任何动态创建和销毁次设备的驱动(USB-到-串口 驱动, USB 猫驱动, USB 蓝牙 tty 驱动, 以及标准串口设备)都是有益的。
      • TTY_DRIVER_INSTALLED:该标志由tty核心在tty驱动被注册后自动设置,且不得由tty驱动设置
  • 自定义的tty设备结构体

    struct tiny_serial
    {struct tty_struct *tty;    /* pointer to the tty for this device */int open_count;             /* number of times this port has been opened */struct semaphore sem;        /* locks this structure */struct timer_list *timer;
    };
    

1. 驱动的创建和初始化

任何一个 tty 驱动的主要数据结构是 struct tty_driver。它用来注册和注销一个 tty 驱动到 tty 内核,在内核头文件<linux/tty_driver.h> 中描述 。

  • alloc_tty_driver()

    /* allocate the tty driver */
    tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
    if (!tiny_tty_driver)return -ENOMEM;
    
  • tty_set_operation()

    tiny_open()
    {...
    }tiny_close()
    {...
    }tiny_write()
    {...
    }tiny_write_room()
    {...
    }tiny_set_termios()
    {...
    }static struct tty_operations serial_ops = {.open = tiny_open,.close = tiny_close,.write = tiny_write,.write_room = tiny_write_room,.set_termios = tiny_set_termios,
    };
    ...
    /* initialize the tty driver */
    tiny_tty_driver->owner = THIS_MODULE;
    tiny_tty_driver->driver_name = "tiny_tty";
    tiny_tty_driver->name = "ttty";
    tiny_tty_driver->devfs_name = "tts/ttty%d";
    tiny_tty_driver->major = TINY_TTY_MAJOR;
    tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
    tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL;
    tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
    tiny_tty_driver->init_termios = tty_std_termios;
    tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    tty_set_operations(tiny_tty_driver, &serial_ops);
    
  • tty_operations

    int (*open)(struct tty_struct * tty, struct file * filp);  //open 函数.
    void (*close)(struct tty_struct * tty, struct file * filp);  //close 函数.
    int (*write)(struct tty_struct * tty, const unsigned char *buf, int count);//write 函数./** 单字节写函数. 这个函数被 tty 核心调用当单个字节被写入设备. 如果* 一个 tty 驱动没有定义这个函数, write 函数被调用来替代, 当 tty* 核心想发送一个单个字节.
    */
    void (*put_char)(struct tty_struct *tty, unsigned char ch);/* 刷新数据到硬件的函数.*/
    void (*flush_chars)(struct tty_struct *tty);
    void (*wait_until_sent)(struct tty_struct *tty, int timeout);int (*write_room)(struct tty_struct *tty);  //指示多少缓冲空闲的函数int (*chars_in_buffer)(struct tty_struct *tty);    //指示多少缓冲满数据的函数.int (*ioctl)(struct tty_struct *tty, struct file * file,unsigned int cmd, unsigned long arg);   //ioctl 函数//当设备的 termios 设置已被改变时.这个函数被 tty 核心调用
    void (*set_termios)(struct tty_struct *tty, struct termios * old);/** 数据抑制函数. 这些函数用来帮助控制 tty 核心的输入缓存. 这个抑制* 函数被调用当 tty 核心的输入缓冲满. tty 驱动应当试图通知设备不应* 当发送字符给它. unthrottle 函数被调用当 tty 核心的输入缓冲已被* 清空, 并且它现在可以接收更多数据. tty 驱动应当接着通知设备可以* 接收数据. stop 和 start 函数非常象 throttle 和 unthrottle 函数,* 但是它们表示 tty 驱动应当停止发送数据给设备以及以后恢复发送数据.*/
    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);   //挂起函数/** 线路中断控制函数. 当这个 tty 驱动在 RS-232 端口上要打开或关闭线路的 BREAK 状态,* 这个函数被调用. 如果状态设为 -1, BREAK 状态应当打开. 如果状态设为 0, BREAK 状态应当关闭.* 如果这个函数由 tty 驱动实现, tty 核心将处理 TCSBRK, TCSBRKP, TIOCSBRK, 和 TIOCCBRK* ioctl. 否则, 这些 ioctls 被发送给驱动 ioctl 函数.*/
    void (*break_ctl)(struct tty_struct *tty, int state);void (*flush_buffer)(struct tty_struct *tty);   //刷新缓冲和丢失任何剩下的数据.void (*set_ldisc)(struct tty_struct *tty); //设置线路规程的函数. 当 tty 核心已改变这个 tty 驱动的线路规程,这个函数被调用. 它通常不用并且不应当被一个驱动定义./** 发送 X-类型 字符 的函数. 这个函数用来发送一个高优先级 XON 或者* XOFF 字符给 tty 设备. 要被发送的字符在 ch 变量中指定.*/
    void (*send_xchar)(struct tty_struct *tty, char ch);/* proc 读和写函数.*/
    int (*read_proc)(char *page, char **start, off_t off, int count, int *eof, void data);
    int (*write_proc)(struct file *file, const char *buffer, unsigned long count, void *data);int (*tiocmget)(struct tty_struct *tty, struct file *file); //获得当前的特定 tty 设备的线路设置. /* 设置当前的特定 tty 设备的线路设置. set 和 clear 包含了去设置或者清除的不同的线路设置 */
    int (*tiocmset)(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear);
    

2. 驱动的注册/注销

  • tty_register_driver() / tty_unregister_driver()

    /* register the tty driver */
    retval = tty_register_driver(tiny_tty_driver);
    if (retval)
    {printk(KERN_ERR "failed to register tiny tty driver");put_tty_driver(tiny_tty_driver);return retval;
    }/* unregister the tty driver */
    tty_unregister_driver(tiny_tty_driver);
    
    • 此时,内核在sysfs中为该驱动创建了所有可能的整个范围内的次设备。
    • 当未指定TTY_DRIVER_NO_DEVFS 标志时,驱动接下来调用函数注册它控制的tty设备
  • tty_register_device() / tty_unregister_device()/

    /* register the tty devices */
    for (i = 0; i < TINY_TTY_MINORS; ++i)tty_register_device(tiny_tty_driver, i, NULL);/* unregister the tty devices */
    for (i = 0; i < TINY_TTY_MINORS; ++i)tty_unregister_device(tiny_tty_driver, i);
    
    • 第1个参数是一个指向设备所属的 struct tty_driver。
    • 第2个参数是设备的次编号;
    • 第3个参数是一个指向这个 tty 设备所绑定的 struct device。如果这个 tty设备没绑定到任何一个 struct device, 这个参数可被设为 NULL。

3. struct termios

这个 struct termios 结构用来描述某个 tty 设备的一个特定端口当前的线路规程设置(波特率, 数据大小, 数据流控…)。在tty_driver结构体中的init_termios变量就是该结构类型,用于描述一个完全的线路规程设置,且驱动初始化时会调用一个标准的数值集合(tty_std_termios)来初始化该init_termios变量。

struct termios tty_std_termios = {.c_iflag = ICRNL | IXON,                  //输入模式标志.c_oflag = OPOST | ONLCR,                   //输出模式标志.c_cflag = B38400 | CS8 | CREAD | HUPCL,    //控制模式标志.c_lflag = ISIG | ICANON | ECHO | ECHOE |  //本地模式标志ECHOK | ECHOCTL | ECHOKE | IEXTEN,.c_cc = INIT_C_CC                         //一个控制字符数组
};
  • 关于各模式标志的详情可参考Linux 发布中的 termios 手册页;
  • 内核提供了一套宏定义来获得不同的位,这些宏定义在头文件 include/linux/tty.h 中定义;

当一个用户要改变一个 tty 设备的线路设置或者获取当前线路设置时,要么调用用户空间下的termios 系列库函数,要么直接对节点调用ioctl。tty核心将自动为大部分 termios 用户空间函数转换为一个对驱动节点的 ioctl 调用。大量的不同的 tty ioctl 调用接着被 tty 核心转换为一个对 tty 驱动的单个set_termios 函数调用。set_termios 调用需要决定哪个线路设置它被请求来改变, 接着在 tty 设备中做这些改变。

  • set_termios

    • 一个 set_termios 函数应当做的第一件事情是决定任何事情是否真的需要改变,可使用下面的代码完成 :

      #define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))unsigned int cflag;
      cflag = tty->termios->c_cflag;
      /* check that they really want us to change something */
      if (old_termios)    //old_termios 变量在它被存取之前,需要被检查是否指向一个有效的结构
      {if ((cflag == old_termios->c_cflag) && (RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag))) {printk(KERN_DEBUG " - nothing to change...\n");return;}
      }/* 为查看需要的字节大小, CSIZE 位掩码可用来从 cflag 变量区分出正确的位*  PARODD 位掩码可用来决定是否奇偶应当是奇或者偶*/
      switch (cflag & CSIZE)
      {case CS5:printk(KERN_DEBUG " - data bits = 5\n");break;case CS6:printk(KERN_DEBUG " - data bits = 6\n");break;case CS7:printk(KERN_DEBUG " - data bits = 7\n");break;default:case CS8:printk(KERN_DEBUG " - data bits = 8\n");break;
      }/* 为决定需要的奇偶值, PARENB 位掩码可对 cflag 变量检查来告知是否任何奇偶要被设置. */
      if (cflag & PARENB)if (cflag & PARODD)printk(KERN_DEBUG " - parity = odd\n");elseprintk(KERN_DEBUG " - parity = even\n");
      elseprintk(KERN_DEBUG " - parity = none\n");
      /* 请求的停止位也可使用 CSTOPB 位掩码从 cflag 变量中来知道 */
      if (cflag & CSTOPB)printk(KERN_DEBUG " - stop bits = 2\n");
      elseprintk(KERN_DEBUG " - stop bits = 1\n");/* 为确定是否用户要求硬件流控,CRTSCTS 位掩码用来对 cflag 变量检查 */
      if (cflag & CRTSCTS)printk(KERN_DEBUG " - RTS/CTS is enabled\n");
      elseprintk(KERN_DEBUG " - RTS/CTS is disabled\n");/* 确定软件流控的不同模式和不同的起停字符是有些复杂 */
      if (I_IXOFF(tty) || I_IXON(tty))
      {unsigned char stop_char = STOP_CHAR(tty);unsigned char start_char = START_CHAR(tty);/* if we are implementing INBOUND XON/XOFF */if (I_IXOFF(tty))printk(KERN_DEBUG " - INBOUND XON/XOFF is enabled, ""XON = %2x, XOFF = %2x", start_char, stop_char);elseprintk(KERN_DEBUG" - INBOUND XON/XOFF is disabled");/* if we are implementing OUTBOUND XON/XOFF */if (I_IXON(tty))printk(KERN_DEBUG" - OUTBOUND XON/XOFF is enabled, ""XON = %2x, XOFF = %2x", start_char, stop_char);elseprintk(KERN_DEBUG" - OUTBOUND XON/XOFF is disabled");
      }/* 最后, 波特率需要确定. tty 核心提供了一个函数, tty_get_baud_rate, 来帮助做这个 */
      printk(KERN_DEBUG " - baud rate = %d", tty_get_baud_rate(tty));
      
      • 至此,tty 驱动已经确定了所有的不同的线路设置, 它可以基于这些值正确设置硬件.
  • tiocmget

    • 当核心需要知道当前的特定tty 设备的控制线的物理值(串口的DTR和RTS线的值),tty 驱动中的 iocmget 函数被 tty 核心所调用。

    • 如果 tty 驱动不能直接读串口的 MSR 或者 MCR 寄存器, 一个它们的拷贝应当在本地保持. 许多 USB-到-串口 驱动必须实现这类的"影子"变量.

      static int tiny_tiocmget(struct tty_struct *tty, struct file *file)
      {struct tiny_serial *tiny = tty->driver_ data;unsigned int result = 0;unsigned int msr = tiny->msr;unsigned int mcr = tiny->mcr;result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0) |  /* DTR is set */((mcr & MCR_RTS) ? TIOCM_RTS : 0 ) |    /* RTS is set */((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) |   /* LOOP is set */((msr & MSR_CTS) ? TIOCM_CTS : 0) |    /* CTS is set */((msr & MSR_CD) ? TIOCM_CAR : 0)|       /* Carrier detect is set */((msr & MSR_RI) ? TIOCM_RI : 0) |        /* Ring Indicatoris set */((msr & MSR_DSR) ? TIOCM_DSR : 0);        /* DSR is set */return result;
      }
      
  • tiocmset

    • 当核心要设置一个特定tty 设备的控制线值,tty 驱动中的 tiocmset 函数被 tty 核心调用。

    • tty 核心通过传递 set 和 clear 告知 tty 驱动设置什么值和清理什么 ,这些变量包含一个应当改变的线路设置的位掩码。

      static int tiny_tiocmset(struct tty_struct *tty, struct file *file,unsigned int set , unsigned int clear)
      {struct tiny_serial *tiny = tty->driver_data;unsigned int mcr = tiny->mcr;if (set & TIOCM_RTS)mcr |= MCR_RTS;if (set & TIOCM_DTR)mcr |= MCR_RTS;if (clear & TIOCM_RTS)mcr &= ~MCR_RTS;if (clear & TIOCM_DTR)mcr &= ~MCR_RTS;/* set the new MCR value in the device */tiny->mcr = mcr;return 0;
      }
      

4. ioctls()

当 ioctl() 被在设备节点上调用,struct tty_driver 中的 ioctl 函数被 tty 核心调用。如果这个 tty 驱动不知道如何处理传递给它的 ioctl 值, 它应当返回 -ENOIOCTLCMD 来试图让 tty 核心实现一个通用的调用版本。

2.6 内核定义了大约 70 个不同的 tty ioctls,大部分的 tty 驱动不全部处理它们, 但是处理一个小的子集。下面讲述一个更通用的 tty ioctls 列表,以及如何实现它们:

cmd 涵义
TIOCSERGETLSR 获得 tty 设备的线路状态寄存器( LSR )的值
TIOCGSERIAL 获得串口线信息,调用者传递一个指向一个大的 serial_struct 结构的指针,这个结构应当由 tty 驱动填充正确的值
TIOCSSERIAL 设置串口线路信息,一个指向 struct serial_struct 的指针被传递给这个调用
TIOCMIWAIT 等待 MSR 改变
TIOCGICOUNT 中断计数(已经产生多少串口线中断)
static int tiny_ioctl(struct tty_struct *tty, struct file *file,unsigned int cmd, unsigned long arg)
{struct tiny_serial *tiny = tty->driver_data;//获得串口线信息if (cmd == TIOCGSERIAL){struct serial_struct tmp;if (!arg)return -EFAULT;memset(&tmp, 0, sizeof(tmp));tmp.type = tiny->serial.type;tmp.line = tiny->serial.line;tmp.port = tiny->serial.port;tmp.irq = tiny->serial.irq;tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ;tmp.xmit_fifo_size = tiny->serial.xmit_fifo_size;tmp.baud_base = tiny->serial.baud_base;tmp.close_delay = 5*HZ;tmp.closing_wait = 30*HZ;tmp.custom_divisor = tiny->serial.custom_divisor;tmp.hub6 = tiny->serial.hub6;tmp.io_type = tiny->serial.io_type;if (copy_to_user((void __user *)arg, &tmp, sizeof(tmp)))return -EFAULT;return 0;}/* arg 参数包含用户在等待的事件类型,当实现这个ioctl时不要使用interruptible_sleep_on调用.* 在 tty 驱动的代码中能知道 MSR 寄存器改变的某些地方, * 必须调用wake_up_interruptible(&tp->wait);以便这个代码能正常工作:*/if (cmd == TIOCMIWAIT){DECLARE_WAITQUEUE(wait, current);struct async_icount cnow;struct async_icount cprev;cprev = tiny->icount;while (1) {add_wait_queue(&tiny->wait, &wait);set_current_state(TASK_INTERRUPTIBLE);schedule();remove_wait_queue(&tiny->wait, &wait);/* see if a signal woke us up */if (signal_pending(current))return -ERESTARTSYS;cnow = tiny->icount;if (cnow.rng == cprev.rng &&cnow.dsr == cprev.dsr &&cnow.dcd == cprev.dcd &&cnow.cts == cprev.cts){return -EIO; /* no change => error */}                if(((arg & TIOCM_RNG) && (cnow.rng !=cprev.rng)) ||((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr))||((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||((arg & TIOCM_CTS)&& (cnow.cts != cprev.cts)) ){return 0;}cprev = cnow;}}/* 这个调用通过arg向内核传递一个serial_icounter_struct 的指针,常常和之前的 IOCMIWAIT ioctl 调用结合使用 */if (cmd == TIOCGICOUNT){struct async_icount cnow = tiny->icount;struct serial_icounter_struct icount;icount.cts = cnow.cts;icount.dsr = cnow.dsr;icount.rng = cnow.rng;icount.dcd = cnow.dcd;icount.rx = cnow.rx;icount.tx = cnow.tx;icount.frame = cnow.frame;icount.overrun = cnow.overrun;icount.parity = cnow.parity;icount.brk = cnow.brk;icount.buf_overrun = cnow.buf_overrun;if (copy_to_user((void __user *)arg, &icount, sizeof(icount)))return -EFAULT;return 0;}return -ENOIOCTLCMD;
}

5. open()和close()

  • 当一个用户对某个 tty 设备节点调用 open 时, tty 核心调用tty_operationsopen 中注册的open函数。该函数使用tty_struct和file指针作为参数,并将一些数据(可以基于端口次编号来引用的静态数组中的数据 )到tty_struct中,以便被用于后续的close, write 使用。下面的例子显示了tiny_tty 驱动中的open函数保存一个指针(用户自定义设备结构体tiny)到 tty 结构中。

    static int tiny_open(struct tty_struct *tty, struct file *file)
    {struct tiny_serial *tiny;      //用户自定义的设备结构体tiny_serialstruct timer_list *timer;int index;/* initialize the pointer in case something fails */tty->driver_data = NULL;/* get the serial object associated with this tty pointer */index = tty->index;tiny = tiny_table[index];if (tiny == NULL){/* first time accessing this device, let's create it */tiny = kmalloc(sizeof(*tiny), GFP_KERNEL);if (!tiny)return -ENOMEM;init_MUTEX(&tiny->sem);tiny->open_count = 0;tiny->timer = NULL;tiny_table[index] = tiny;}down(&tiny->sem);/* save our structure within the tty structure *//* tiny_serial 结构被保存在 tty 结构中. * 允许 tiny_write,tiny_write_room, 和 tiny_close 函数来获取 tiny_serial 结构和正确操作它*/tiny->tty = tty;tty->driver_data = tiny; /* trace the tty device opened times */++tiny->open_count;if (tiny->open_count == 1){/* 若设备是第一次被打开, 还需要做任何必要的硬件初始化和内存分配工作 */...}return 0;
    }
    
  • 用户对前面使用 open 调用而创建的文件句柄调用 close 时,tty 核心调用tty_operationsopen 中注册的close函数。因为 open 函数可被多次调用, close 函数也可多次调用. 因此这个函数应当跟踪它被调用的次数来决定是否硬件应当在此次真正被关闭。

    static void do_close(struct tiny_serial *tiny)
    {down(&tiny->sem);if (!tiny->open_count){/* port was never opened */goto exit;}--tiny->open_count;if (tiny->open_count <= 0){/* The port is being closed by the last user. *//* Do any hardware specific stuff here */.../* shut down our timer */del_timer(tiny->timer);}
    exit:up(&tiny->sem);
    }static void tiny_close(struct tty_struct *tty, struct file *file)
    {struct tiny_serial *tiny = tty->driver_data;if (tiny)do_close(tiny);
    }
    
    • close 函数没有返回值,因为它不被认为会失败。

6. write

用户在有数据发送给硬件时,write 函数被调用。但不是所有的写程序要求的字符都可以在调用写函数时被发送,因为还需要考虑速度匹配和 tty 硬件的缓冲区容量大小。函数返回能够发送给硬件的字符数,如果发生写错误则返回负的错误码。

  • write()

write 函数被要求可在中断上下文和用户上下文中被调用。所以tty驱动不应当调用任何可引起睡眠的函数(copy_from_user、kmalloc、printk等)。内核提供了一个函数calling_in_interrupt来判断驱动是否处于在中断上下文中。

下面的代码显示了一个简单的写函数,因为没有实际的tty硬件,故我们将要写的数据记录到内核调试日志中

static int tiny_write(struct tty_struct *tty, const unsigned char *buffer, int count)
{struct tiny_serial *tiny = tty->driver_data;int i;int ret = -EINVAL;if (!tiny)return -ENODEV;down(&tiny->sem);if (!tiny->open_count)/* port was not opened */goto exit;/* fake sending the data out a hardware port by* writing it to the kernel debug log.*/printk(KERN_DEBUG "%s - ", __FUNCTION__);for (i = 0; i < count; ++i)printk("%02x ", buffer[i]);printk("\n");
exit:up(&tiny->sem);return ret;
}

如果tty 驱动在 tty_struct 中没有实现 put_char 函数,tty 核心用一个数据大小为 1 来使用 write 函数回调。这普遍发生在tty 核心想转换一个新行字符为一个换行,此时write函数不得将数据进行缓冲和重试。但因为 write 函数不知道是否它在被调用来替代 put_char,因此,一些终端类型(例如很多USB-到-串口 )不能正确工作。

  • write_room()

    • 当 tty 核心想知道多少空间在写缓冲中时,可调用此函数。随着字符不断从写缓冲清空以及调用写函数时添加字符到写缓冲,这个数字时时改变的。

      static int tiny_write_room(struct tty_struct *tty)
      {struct tiny_serial *tiny = tty->driver_data;int room = -EINVAL;if (!tiny)return -ENODEV;down(&tiny->sem);if (!tiny->open_count){/* port was not opened */goto exit;}/* calculate how much room is left in the device */...
      exit:up(&tiny->sem);return room;
      }
      

7. 其它缓冲函数

  • chars_in_buffer

    • 当 tty 核心想知道还有多少字符仍然保留在 tty驱动的写缓冲中等待被发送时,调用此函数。
    • 虽然该函数不是tty驱动必须实现的,但如果驱动能够在它发送字符串到硬件之前有能力存储它们到一个写缓存中,那就应该实现它。 另外,在具有缓冲能力的情况下,推荐驱动还要实现下面3个函数,虽然不是必须的。
  • flush_chars()
    • 当 tty 核心使用 put_char 函数已发送了许多字符给 tty 驱动,且 tty 驱动还未启动发送这些字符到硬件时调用。该函数是非阻塞的,即不用等待发送完毕后才返回。
  • wait_until_sent()
    • 该函数与flush_chars()函数的使用情况类似,区别在于它是阻塞的,它会等待字符发送完毕或达到超时时间。当这个超时时间定为0时,函数会一直阻塞等待。
  • flush_buffer()
    • 当 tty 驱动要刷新所有仍然在写缓冲的数据时,该函数被调用。注意,这些被清空的数据直接丢弃,而不是发送到硬件。

8. read哪去了

tty 核心提供一个缓冲逻辑用来存放从硬件收到的数据,但该缓冲区溢出时,并不会通知tty核心或用户。该缓冲区用 struct tty_flip_buffer 来表示。

一个 flip 缓冲是一个包含 2 个主要数据数组的结构. 从 tty 设备接收到的数据被存储于第一个数组. 当这个数组满, 任何等待数据的用户被通知数据可以读. 当用户从这个数组读数据, 任何新到的数据被存储在第 2 个数组。当那个数组被读空, 数据再次刷新给用户, 并且驱动开始填充第 1 个数组。

为试图阻止数据丢失, 一个 tty 驱动可以监视到来的数组多大,并且,如果它添满了,要及时告知 tty 驱动刷新缓冲,而不是等待下一个可用的机会。

tty 驱动 只需要关心struct tty_flip_buffer 结构中的 cout变量,它表示当前缓冲里还可用来接收数据的空间大小。如果这个值等于值 TTY_FLIPBUF_SIZE,这个 flip 缓冲需要被刷新到用户。一个使用cout变量的例子如下:

for (i = 0; i < data_size; ++i)
{if (tty->flip.count >= TTY_FLIPBUF_SIZE)tty_flip_buffer_push(tty);/*将从 tty 驱动接收来的要发送给用户的字符添加到 flip 缓冲*/tty_insert_flip_char(tty, data[i], TTY_NORMAL);
}
tty_flip_buffer_push(tty);
  • tty_insert_flip_char()

    • 该函数将从 tty 驱动接收来的要发送给用户的字符添加到 flip 缓冲。
    • 第1个参数是数据应当保存入的struct tty_struct;
    • 第 2 个参数是要保存的字符;
    • 第 3 个参数是字符类型标志。
      • TTY_NORMAL: 这个是一个正常的被接收的字符.
      • TTY_BREAK、TTY_PARITY、 TTY_OVERRUN:这是一个特殊类型的指示错误接收数据的字符,具体取值取决于错误类型。
  • tty_flip_buffer_push()

    • 当数据被加到 flip 缓冲, 或者当 flip 缓冲满时, tty 驱动调用该函数。
    • 如果 tty 驱动可高速接收数据,且 tty->low_latency标志被设置, 对 tty_flip_buffer_push 的调用被立刻执行。否则,该函数会会调度它自己,在之后的一个时间点将数据推出缓冲。

9. tty_struct

tty_struct 变量被 tty 核心用来保持当前的特定 tty 端口的状态

/*  <linux/tty.h> */
struct tty_struct {int  magic;struct tty_driver *driver;    //当前控制这个 tty 设备的 tty_driver 结构int index;struct tty_ldisc ldisc;     //给 tty 设备的线路规程struct mutex termios_mutex;struct ktermios *termios, *termios_locked; //指向 tty 设备的当前 termios 设置的指针char name[64];struct pid *pgrp;struct pid *session;unsigned long flags;     //tty 设备的当前状态int count;struct winsize winsize;/** stopped: 指示tty 设备是否 被停止. tty 驱动可以设置这个值* hw_stopped:指示tty 设备是否 已经停止. tty 驱动可以设置这个值* low_latency:指示tty 设备是否能够高速接收数据. tty驱动可以设置这个值.*/unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1;    unsigned char low_latency:1, warned:1;unsigned char ctrl_status;unsigned int receive_room;   /* Bytes free for queue */struct tty_struct *link;struct fasync_struct *fasync;struct tty_bufhead buf;int alt_speed;        /* For magic substitution of 38400 bps */wait_queue_head_t write_wait;  //当tty驱动可以接收更多数据时,应当唤醒它wait_queue_head_t read_wait;struct work_struct hangup_work;void *disc_data;void *driver_data;     //用来存储对于 tty 驱动本地的数据. 这个变量不被 tty 核心修改.struct list_head tty_files;#define N_TTY_BUF_SIZE 4096/** The following is data for the N_TTY line discipline.  For* historical reasons, this is included in the tty structure.*/unsigned int column;unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;unsigned char closing:1;    //指示tty 设备是否在关闭端口当中. tty 驱动可以设置这个值unsigned short minimum_to_wake;unsigned long overrun_time;int num_overrun;unsigned long process_char_map[256/(8*sizeof(unsigned long))];char *read_buf;int read_head;int read_tail;int read_cnt;unsigned long read_flags[N_TTY_BUF_SIZE/(8*sizeof(unsigned long))];int canon_data;unsigned long canon_head;unsigned int canon_column;struct mutex atomic_read_lock;struct mutex atomic_write_lock;unsigned char *write_buf;int write_cnt;spinlock_t read_lock;/* If the tty has a pending do_SAK, queue it here - akpm */struct work_struct SAK_work;
};

几乎它的所有条目都只被 tty 核心使用,只有几个例外是被tty驱动使用的,下面一一讲解

  • **flags **:tty 设备的当前状态. 这是一个位段变量, 并且通过下面的宏定义存取

    • TTY_THROTTLED :仅由tty核心设置,表示驱动中有抑制函数被调用
    • TTY_IO_ERROR :当它不想任何数据被读出或写入时,驱动设置该标志。常用在设备被关闭时。
    • TTY_OTHER_CLOSED :当端口已经被关闭时,仅由 pty 驱动使用来设置。
    • TTY_EXCLUSIVE :由 tty 核心设置来指示一个端口在独占模式并且只能一次由一个用户存取。
    • TTY_DO_WRITE_WAKEUP :线路规程的 write_wakeup 函数被允许来被调用,常常在tty_driver 调用 wake_up_interruptible 函数的同一时间被调用
    • TTY_DONT_FLIP :被缺省的 tty 线路规程用来通知 tty 核心,表示不应当改变 flip 缓冲
    • TTY_HW_COOK_OUT :如果它没有被tty 驱动设置, 线路规程成块拷贝驱动的输出,如果被设置, 它通知线路规程应当"烹调"发送给它的输出。这个标志应当通常不被 tty 驱动设置
    • TTY_HW_COOK_IN :几乎和设置在驱动中的 flag 变量中的 TTY_DRIVER_REAL_RAW 标志一致。这个标志通常应当不被 tty 驱动设置.
    • TTY_PTY_LOCK :pty 驱动用来加锁和解锁一个端口
    • TTY_NO_WRITE_SPLIT :如果设置, tty 核心不将对 tty 驱动的写分成正常大小的块

3、一个完整的tty驱动源代码

/** Tiny TTY driver** This driver shows how to create a minimal tty driver.  It does not rely on* any backing hardware, but creates a timer that emulates data being received* from some kind of hardware.*/#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <asm/uaccess.h>#define DRIVER_VERSION "v2.0"
#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>"
#define DRIVER_DESC "Tiny TTY driver"/* Module information */
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");#define DELAY_TIME      HZ * 2  /* 2 seconds per character */
#define TINY_DATA_CHARACTER 't'#define TINY_TTY_MAJOR     240 /* experimental range */
#define TINY_TTY_MINORS     4   /* only have 4 devices */struct tiny_serial {struct tty_struct  *tty;       /* pointer to the tty for this device */int         open_count; /* number of times this port has been opened */struct semaphore sem;        /* locks this structure */struct timer_list *timer;/* for tiocmget and tiocmset functions */int         msr;        /* MSR shadow */int         mcr;        /* MCR shadow *//* for ioctl fun */struct serial_struct serial;wait_queue_head_t    wait;struct async_icount    icount;
};static struct tiny_serial *tiny_table[TINY_TTY_MINORS];   /* initially all NULL */static void tiny_timer(unsigned long timer_data)
{struct tiny_serial *tiny = (struct tiny_serial *)timer_data;struct tty_struct *tty;int i;char data[1] = {TINY_DATA_CHARACTER};int data_size = 1;if (!tiny)return;tty = tiny->tty;/* send the data to the tty layer for users to read.  This doesn't* actually push the data through unless tty->low_latency is set */for (i = 0; i < data_size; ++i) {if (tty->flip.count >= TTY_FLIPBUF_SIZE)tty_flip_buffer_push(tty);tty_insert_flip_char(tty, data[i], TTY_NORMAL);}tty_flip_buffer_push(tty);/* resubmit the timer again */tiny->timer->expires = jiffies + DELAY_TIME;add_timer(tiny->timer);
}static int tiny_open(struct tty_struct *tty, struct file *file)
{struct tiny_serial *tiny;struct timer_list *timer;int index;/* initialize the pointer in case something fails */tty->driver_data = NULL;/* get the serial object associated with this tty pointer */index = tty->index;tiny = tiny_table[index];if (tiny == NULL) {/* first time accessing this device, let's create it */tiny = kmalloc(sizeof(*tiny), GFP_KERNEL);if (!tiny)return -ENOMEM;init_MUTEX(&tiny->sem);tiny->open_count = 0;tiny->timer = NULL;tiny_table[index] = tiny;}down(&tiny->sem);/* save our structure within the tty structure */tty->driver_data = tiny;tiny->tty = tty;++tiny->open_count;if (tiny->open_count == 1) {/* this is the first time this port is opened *//* do any hardware initialization needed here *//* create our timer and submit it */if (!tiny->timer) {timer = kmalloc(sizeof(*timer), GFP_KERNEL);if (!timer) {up(&tiny->sem);return -ENOMEM;}tiny->timer = timer;}tiny->timer->data = (unsigned long )tiny;tiny->timer->expires = jiffies + DELAY_TIME;tiny->timer->function = tiny_timer;add_timer(tiny->timer);}up(&tiny->sem);return 0;
}static void do_close(struct tiny_serial *tiny)
{down(&tiny->sem);if (!tiny->open_count) {/* port was never opened */goto exit;}--tiny->open_count;if (tiny->open_count <= 0) {/* The port is being closed by the last user. *//* Do any hardware specific stuff here *//* shut down our timer */del_timer(tiny->timer);}
exit:up(&tiny->sem);
}static void tiny_close(struct tty_struct *tty, struct file *file)
{struct tiny_serial *tiny = tty->driver_data;if (tiny)do_close(tiny);
}   static int tiny_write(struct tty_struct *tty, const unsigned char *buffer, int count)
{struct tiny_serial *tiny = tty->driver_data;int i;int retval = -EINVAL;if (!tiny)return -ENODEV;down(&tiny->sem);if (!tiny->open_count)/* port was not opened */goto exit;/* fake sending the data out a hardware port by* writing it to the kernel debug log.*/printk(KERN_DEBUG "%s - ", __FUNCTION__);for (i = 0; i < count; ++i)printk("%02x ", buffer[i]);printk("\n");exit:up(&tiny->sem);return retval;
}static int tiny_write_room(struct tty_struct *tty)
{struct tiny_serial *tiny = tty->driver_data;int room = -EINVAL;if (!tiny)return -ENODEV;down(&tiny->sem);if (!tiny->open_count) {/* port was not opened */goto exit;}/* calculate how much room is left in the device */room = 255;exit:up(&tiny->sem);return room;
}#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))static void tiny_set_termios(struct tty_struct *tty, struct termios *old_termios)
{unsigned int cflag;cflag = tty->termios->c_cflag;/* check that they really want us to change something */if (old_termios) {if ((cflag == old_termios->c_cflag) &&(RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag))) {printk(KERN_DEBUG " - nothing to change...\n");return;}}/* get the byte size */switch (cflag & CSIZE) {case CS5:printk(KERN_DEBUG " - data bits = 5\n");break;case CS6:printk(KERN_DEBUG " - data bits = 6\n");break;case CS7:printk(KERN_DEBUG " - data bits = 7\n");break;default:case CS8:printk(KERN_DEBUG " - data bits = 8\n");break;}/* determine the parity */if (cflag & PARENB)if (cflag & PARODD)printk(KERN_DEBUG " - parity = odd\n");elseprintk(KERN_DEBUG " - parity = even\n");elseprintk(KERN_DEBUG " - parity = none\n");/* figure out the stop bits requested */if (cflag & CSTOPB)printk(KERN_DEBUG " - stop bits = 2\n");elseprintk(KERN_DEBUG " - stop bits = 1\n");/* figure out the hardware flow control settings */if (cflag & CRTSCTS)printk(KERN_DEBUG " - RTS/CTS is enabled\n");elseprintk(KERN_DEBUG " - RTS/CTS is disabled\n");/* determine software flow control *//* if we are implementing XON/XOFF, set the start and * stop character in the device */if (I_IXOFF(tty) || I_IXON(tty)) {unsigned char stop_char  = STOP_CHAR(tty);unsigned char start_char = START_CHAR(tty);/* if we are implementing INBOUND XON/XOFF */if (I_IXOFF(tty))printk(KERN_DEBUG " - INBOUND XON/XOFF is enabled, ""XON = %2x, XOFF = %2x", start_char, stop_char);elseprintk(KERN_DEBUG" - INBOUND XON/XOFF is disabled");/* if we are implementing OUTBOUND XON/XOFF */if (I_IXON(tty))printk(KERN_DEBUG" - OUTBOUND XON/XOFF is enabled, ""XON = %2x, XOFF = %2x", start_char, stop_char);elseprintk(KERN_DEBUG" - OUTBOUND XON/XOFF is disabled");}/* get the baud rate wanted */printk(KERN_DEBUG " - baud rate = %d", tty_get_baud_rate(tty));
}/* Our fake UART values */
#define MCR_DTR     0x01
#define MCR_RTS     0x02
#define MCR_LOOP    0x04
#define MSR_CTS     0x08
#define MSR_CD      0x10
#define MSR_RI      0x20
#define MSR_DSR     0x40static int tiny_tiocmget(struct tty_struct *tty, struct file *file)
{struct tiny_serial *tiny = tty->driver_data;unsigned int result = 0;unsigned int msr = tiny->msr;unsigned int mcr = tiny->mcr;result = ((mcr & MCR_DTR)  ? TIOCM_DTR  : 0) | /* DTR is set */((mcr & MCR_RTS)  ? TIOCM_RTS  : 0) |   /* RTS is set */((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) |   /* LOOP is set */((msr & MSR_CTS)  ? TIOCM_CTS  : 0) |  /* CTS is set */((msr & MSR_CD)   ? TIOCM_CAR  : 0) |   /* Carrier detect is set*/((msr & MSR_RI)   ? TIOCM_RI   : 0) | /* Ring Indicator is set */((msr & MSR_DSR)  ? TIOCM_DSR  : 0); /* DSR is set */return result;
}static int tiny_tiocmset(struct tty_struct *tty, struct file *file,unsigned int set, unsigned int clear)
{struct tiny_serial *tiny = tty->driver_data;unsigned int mcr = tiny->mcr;if (set & TIOCM_RTS)mcr |= MCR_RTS;if (set & TIOCM_DTR)mcr |= MCR_RTS;if (clear & TIOCM_RTS)mcr &= ~MCR_RTS;if (clear & TIOCM_DTR)mcr &= ~MCR_RTS;/* set the new MCR value in the device */tiny->mcr = mcr;return 0;
}static int tiny_read_proc(char *page, char **start, off_t off, int count,int *eof, void *data)
{struct tiny_serial *tiny;off_t begin = 0;int length = 0;int i;length += sprintf(page, "tinyserinfo:1.0 driver:%s\n", DRIVER_VERSION);for (i = 0; i < TINY_TTY_MINORS && length < PAGE_SIZE; ++i) {tiny = tiny_table[i];if (tiny == NULL)continue;length += sprintf(page+length, "%d\n", i);if ((length + begin) > (off + count))goto done;if ((length + begin) < off) {begin += length;length = 0;}}*eof = 1;
done:if (off >= (length + begin))return 0;*start = page + (off-begin);return (count < begin+length-off) ? count : begin + length-off;
}#define tiny_ioctl tiny_ioctl_tiocgserial
static int tiny_ioctl(struct tty_struct *tty, struct file *file,unsigned int cmd, unsigned long arg)
{struct tiny_serial *tiny = tty->driver_data;if (cmd == TIOCGSERIAL) {struct serial_struct tmp;if (!arg)return -EFAULT;memset(&tmp, 0, sizeof(tmp));tmp.type      = tiny->serial.type;tmp.line        = tiny->serial.line;tmp.port        = tiny->serial.port;tmp.irq         = tiny->serial.irq;tmp.flags        = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ;tmp.xmit_fifo_size  = tiny->serial.xmit_fifo_size;tmp.baud_base     = tiny->serial.baud_base;tmp.close_delay        = 5*HZ;tmp.closing_wait    = 30*HZ;tmp.custom_divisor = tiny->serial.custom_divisor;tmp.hub6      = tiny->serial.hub6;tmp.io_type     = tiny->serial.io_type;if (copy_to_user((void __user *)arg, &tmp, sizeof(struct serial_struct)))return -EFAULT;return 0;}return -ENOIOCTLCMD;
}
#undef tiny_ioctl#define tiny_ioctl tiny_ioctl_tiocmiwait
static int tiny_ioctl(struct tty_struct *tty, struct file *file,unsigned int cmd, unsigned long arg)
{struct tiny_serial *tiny = tty->driver_data;if (cmd == TIOCMIWAIT) {DECLARE_WAITQUEUE(wait, current);struct async_icount cnow;struct async_icount cprev;cprev = tiny->icount;while (1) {add_wait_queue(&tiny->wait, &wait);set_current_state(TASK_INTERRUPTIBLE);schedule();remove_wait_queue(&tiny->wait, &wait);/* see if a signal woke us up */if (signal_pending(current))return -ERESTARTSYS;cnow = tiny->icount;if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)return -EIO; /* no change => error */if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||((arg & TIOCM_CD)  && (cnow.dcd != cprev.dcd)) ||((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {return 0;}cprev = cnow;}}return -ENOIOCTLCMD;
}
#undef tiny_ioctl#define tiny_ioctl tiny_ioctl_tiocgicount
static int tiny_ioctl(struct tty_struct *tty, struct file *file,unsigned int cmd, unsigned long arg)
{struct tiny_serial *tiny = tty->driver_data;if (cmd == TIOCGICOUNT) {struct async_icount cnow = tiny->icount;struct serial_icounter_struct icount;icount.cts = cnow.cts;icount.dsr  = cnow.dsr;icount.rng  = cnow.rng;icount.dcd  = cnow.dcd;icount.rx   = cnow.rx;icount.tx    = cnow.tx;icount.frame = cnow.frame;icount.overrun    = cnow.overrun;icount.parity   = cnow.parity;icount.brk   = cnow.brk;icount.buf_overrun = cnow.buf_overrun;if (copy_to_user((void __user *)arg, &icount, sizeof(icount)))return -EFAULT;return 0;}return -ENOIOCTLCMD;
}
#undef tiny_ioctl/* the real tiny_ioctl function.  The above is done to get the small functions in the book */
static int tiny_ioctl(struct tty_struct *tty, struct file *file,unsigned int cmd, unsigned long arg)
{switch (cmd) {case TIOCGSERIAL:return tiny_ioctl_tiocgserial(tty, file, cmd, arg);case TIOCMIWAIT:return tiny_ioctl_tiocmiwait(tty, file, cmd, arg);case TIOCGICOUNT:return tiny_ioctl_tiocgicount(tty, file, cmd, arg);}return -ENOIOCTLCMD;
}static struct tty_operations serial_ops = {.open = tiny_open,.close = tiny_close,.write = tiny_write,.write_room = tiny_write_room,.set_termios = tiny_set_termios,
};static struct tty_driver *tiny_tty_driver;static int __init tiny_init(void)
{int retval;int i;/* allocate the tty driver */tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);if (!tiny_tty_driver)return -ENOMEM;/* initialize the tty driver */tiny_tty_driver->owner = THIS_MODULE;tiny_tty_driver->driver_name = "tiny_tty";tiny_tty_driver->name = "ttty";tiny_tty_driver->devfs_name = "tts/ttty%d";tiny_tty_driver->major = TINY_TTY_MAJOR,tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL,tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,tiny_tty_driver->init_termios = tty_std_termios;tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;tty_set_operations(tiny_tty_driver, &serial_ops);/* hack to make the book purty, yet still use these functions in the* real driver.  They really should be set up in the serial_ops* structure above... */tiny_tty_driver->read_proc = tiny_read_proc;tiny_tty_driver->tiocmget = tiny_tiocmget;tiny_tty_driver->tiocmset = tiny_tiocmset;tiny_tty_driver->ioctl = tiny_ioctl;/* register the tty driver */retval = tty_register_driver(tiny_tty_driver);if (retval) {printk(KERN_ERR "failed to register tiny tty driver");put_tty_driver(tiny_tty_driver);return retval;}for (i = 0; i < TINY_TTY_MINORS; ++i)tty_register_device(tiny_tty_driver, i, NULL);printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION);return retval;
}static void __exit tiny_exit(void)
{struct tiny_serial *tiny;int i;for (i = 0; i < TINY_TTY_MINORS; ++i)tty_unregister_device(tiny_tty_driver, i);tty_unregister_driver(tiny_tty_driver);/* shut down all of the timers and free the memory */for (i = 0; i < TINY_TTY_MINORS; ++i) {tiny = tiny_table[i];if (tiny) {/* close the port */while (tiny->open_count)do_close(tiny);/* shut down our timer and free the memory */del_timer(tiny->timer);kfree(tiny->timer);kfree(tiny);tiny_table[i] = NULL;}}
}module_init(tiny_init);
module_exit(tiny_exit);

【参考文章】

  • Linux终端和Line discipline图解

  • tty驱动初步了解学习
    river->init_termios = tty_std_termios;
    tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    tty_set_operations(tiny_tty_driver, &serial_ops);

    /* hack to make the book purty, yet still use these functions in the

    • real driver. They really should be set up in the serial_ops
    • structure above… */
      tiny_tty_driver->read_proc = tiny_read_proc;
      tiny_tty_driver->tiocmget = tiny_tiocmget;
      tiny_tty_driver->tiocmset = tiny_tiocmset;
      tiny_tty_driver->ioctl = tiny_ioctl;

    /* register the tty driver */
    retval = tty_register_driver(tiny_tty_driver);
    if (retval) {
    printk(KERN_ERR “failed to register tiny tty driver”);
    put_tty_driver(tiny_tty_driver);
    return retval;
    }

    for (i = 0; i < TINY_TTY_MINORS; ++i)
    tty_register_device(tiny_tty_driver, i, NULL);

    printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION);
    return retval;
    }

static void __exit tiny_exit(void)
{
struct tiny_serial *tiny;
int i;

for (i = 0; i < TINY_TTY_MINORS; ++i)tty_unregister_device(tiny_tty_driver, i);
tty_unregister_driver(tiny_tty_driver);/* shut down all of the timers and free the memory */
for (i = 0; i < TINY_TTY_MINORS; ++i) {tiny = tiny_table[i];if (tiny) {/* close the port */while (tiny->open_count)do_close(tiny);/* shut down our timer and free the memory */del_timer(tiny->timer);kfree(tiny->timer);kfree(tiny);tiny_table[i] = NULL;}
}

}

module_init(tiny_init);
module_exit(tiny_exit);

【参考文章】- [Linux终端和Line discipline图解](https://blog.csdn.net/dog250/article/details/78818612)
- [tty驱动初步了解学习](https://blog.csdn.net/zzsddre/article/details/125392085)
- LLD3

一文彻底讲清Linux tty子系统架构及编程实例相关推荐

  1. linux TTY子系统 --- bug来了,稍候再整理

    Version: 0.9 Date: 2018-09-27 Nemo.Han 目录 1.学习出发点及目标 1.1 学习出发点 1.2 学习目标 2 什么是TTY 2.1 串口 2.2 伪终端 2.3 ...

  2. linux TTY子系统(2) -- 软件框架

    了解tty 子系统 1.TTY的子系统   在Linux kernel中,TTY就是各类终端(Terminal)的简称.为了简化终端的使用,以及终端驱动程序的编写,Linux kernel抽象出了TT ...

  3. Linux TTY子系统(1) - 了解TTY

    了解linux tty 1.TTY概述   TTY 是 Teletype 或 Teletypewriter 的缩写,原来是指电传打字机,后来这种设备逐渐键盘和显示器取代.不管是电传打字机还是键盘显示器 ...

  4. linux TTY子系统(3) - tty driver

    了解linux tty driver 1.TTY device   kernel从设备模型和字符设备两个角度对它进行了抽象: 设备模型的角度   为每个"数据通道"注册了一个stu ...

  5. linux tty结构体,linux tty驱动架构分析

    再看Linux tty驱动过程中发现linux的驱动构架中,面向对象的思想已经根深蒂固.就比如这串口驱动,代码中经常有一些貌似和串口无关的代码,比如,tty_register_driver等.但我们却 ...

  6. 一文讲清,MySQL主从架构

    MySQL在生成环境中,如果是单机版的部署方式,就会有很大的可用性问题,MySQL提供了一套主从复制的架构,以提高其可用性. MySQL主从复制架构,就是部署两台机器,一台机器上部署的MySQL是ma ...

  7. 新制式轨道交通有哪些?这篇推文全讲清了

    -- 中低速磁浮 -- 国家于2016年启动"十三五"重点研发计划,先进轨道交通重点专项"磁浮交通系统关键技术":具体包括时速600公里的高速磁浮项目与时速20 ...

  8. Linux输入子系统:输入设备编程指南 -- input-programming.txt

    输入设备编程指南(Programming input drivers) ~~~~~~~~~~~~~~~~~~~~~~~~~ 1. 新建一个输入设备驱动程序 ~~~~~~~~~~~~~~~~~~~~~~ ...

  9. linux iopen i2c dev,i2c-dev - Linux驱动子系统之I2C_Linux编程_Linux公社-Linux系统门户网站...

    [概述] 之前在介绍I2C子系统时,提到过使用i2c-dev.c文件在应用程序中实现我们的I2C从设备驱动.不过,它实现的是一个虚拟,临时的i2c_client,随着设备文件的打开而产生,并随着设备文 ...

最新文章

  1. jenkins页面中英文切换配置
  2. 剑指offer 算法 (画图让抽象问题形象化)
  3. (33)调试驱动程序
  4. java 获取继承字段_java – 从类中获取所有字段(甚至是私有的和继承的)
  5. Android笔记 fragment通信
  6. word2003快速排版工具栏_技巧 | 快速又好看,PPT制作不再难
  7. 百度echars 插件 横坐标信息倾斜显示
  8. 怎样快速学会python_python入门如何更快的学习
  9. canopy java_在Windows上安装带有Enthought Canopy的Theano
  10. wide and deep及其发展
  11. 微软运行库合集 |VC9、VC11、VC14、VC15库 32位 64位|v2019.3.2(3264位)最新版 phpstudy vc9-vc14运行库
  12. 《商务与经济统计》(三)
  13. 车辆轨迹跟踪算法---几何跟踪算法
  14. python语言的多行注释以什么开头和结尾_python多行注释
  15. 【平面设计基础】03:绘制—图标到底有多重要
  16. 92_目标:2019年底博客访问量达到10W+
  17. dcs常用的冗余方式_DCS冗余技术简介
  18. 抖音无人直播技术手机版,增加多平台实时在线转播功能,无人直播防屏蔽防检测
  19. springMVC和mybatis的框架搭建
  20. Ant Design Pro初探

热门文章

  1. 2020复旦cs专硕抄底经验
  2. Rufus 格式化和创建可引导U盘的工具
  3. 外卖返利小程序系统公众号外卖饿了么美团cps返利小程序分销系统
  4. 白话数字签名(3)—Web程序中的数字签名
  5. Taylor formulation
  6. 万能通用的各大网站(全民简历、简历本等) 简历模板 -- 免费下载方法
  7. 安卓开发:实现调用相机拍照
  8. AFC中央计算机系统图,干货丨城市地铁AFC系统由哪些部分组成的?如何运作?
  9. (转)某618大促项目的复盘总结
  10. 迎战618大促 京东技术大“阅兵”