TTY设备驱动结构

本文为你展示基于“串口通信设备”的TTY设备驱动程序内部结构,并讲述这些驱动是如何实现链路层通信协议(包括ppp和slip)的应用 。本文的代码基于2.4内核,大部分适用于2.2和2.0[注] 。
注:本文的代码虽然基于2.4内核,但是结构原理在现在内核版本仍然存在 。
串口驱动误解
当我们说【串口驱动】的时候,我们第一个想到的东西是/dev/ttyS0,因为它是大家所熟知的串口通信的设备文件(至少在PC系统上如此) 。由于/dev/ttyS0是一个字符型的设备文件,大家都以为串口驱动是字符设备驱动,这种推断不能叫错,只是不够准确 。【串口驱动】不是一般的字符驱动,有两点可以证明:
串口驱动的“串口”从分类就看出来它是一种特殊的驱动类型,归于字符驱动大类下 。
查看 /proc/ ,你会发现ttyS驱动的主设备号是4,这是一个善意的谎言:文本模式控制台设备的主设备号也是4 。事实上,2.0版内核用了一个通用的名字“ttyp”代表主设备号4 。
串口设备之所以与一般的字符设备(比如并口打印机或磁带机)存在不同是因为串口被用来实现高一级的抽象——tty,串口设备为tty设备提供了串行通信信道 。tty的名字来源早期的一种串口应用设备——电传打字机(tele-type) 。
tty设备驱动子系统
tty设备是什么样的设备呢?tty的概念其实已经被泛化,不只是指处理数字文字()字符的终端设备,例如基于VGA和帧缓冲式的文本控制台、xterm虚拟终端等 。[tty设备驱动子系统]以下图那样的一种结构实现了对tty设备的泛化 。某特定tty设备驱动的特殊部分以注册的方式注册入通用的子系统部分,从而实现一支特定tty设备驱动 。注册方式的好处是设备驱动的特殊部分可以以内核模块形式( )实现 。
【TTY设备驱动结构】 1
图中展示出有三部分是可注册的,串行通信设备驱动(.c)、线路规则(n_tty.c)和[tty设备驱动子系统]本身(.c) 。可以这样理解,tty设备是一种特殊的字符设备,所以它需通过[字符设备驱动子系统](fs/.c)注册自身 。[串行通信设备驱动]是不能被用户直接使用的,它必须抽象为一个tty设备,然后配置使用默认的线路规则——n_tty.c,经[tty设备驱动子系统]在系统中注册为字符设备 。
由此可见,[tty设备驱动子系统]被划分为三块,数据流从用户空间到串行设备间夹着一层tty 。虽然如此,.c本身只是桥梁,实际的tty操作是线路规则完成,线路规则(line )是一支定义串口线如何使用的软件模块 。Linux默认是线路规则是N_TTY,N_TTY是标准字符终端IO处理规则 。
为何如此复杂?

TTY设备驱动结构

文章插图
这种分层设计看上去就很复杂,有必要吗?复杂的回报是灵活性 。当tty设备应用种类纷繁时,很有必要 。为新串行设备编写驱动比一般字符设备要复杂,有了这种通用抽象模型,为编写串口设备驱动省下很多功夫 。
更重一点是,[tty设备驱动子系统]把线路规则也卸下,进一步的抽象后,线路规则也可替换 。这样,串行设备驱动根本不知道数据是从哪来的,它只管收发就行了 。
PPP和slip
如果你使用modem(使用PPP链路协议)拨号上网,或者用SLIP互联PC和你的掌上PDA,你就体会到上面提到的复杂性 。ppp和SLIP实现了各自的线路规则,当它们中任何一个应用要运行,tty设备必须在它们的线路规则模块间切换,来构造不同的tty设备 。
下图展示了SLIP应用的一个概念图 。
2
有趣的是,SLIP驱动向[tty设备驱动子系统]和[网络子系统]同时注册,前者是线路规则,后者网络设备slip0 。当tty设备切换为后,串行通信数据经TCP/IP协议栈与用户空间通信 。当tty设备被切为TCP/IP网络设备后,其它用户进程没法读写 基于/dev/ttyS的tty设备 。这也说明了为什么网络通道建立后,和 pppd 都不能退出的原因 。