精华内容
下载资源
问答
  • 对于Linux内核tty设备的一点理解,可以初学者更加的理解linux下的TTY
  • Linuxtty设备介绍

    千次阅读 2018-12-17 21:13:29
    本文转载于:对于Linux内核tty设备的一点理解 目录 前言 一、终端按照其自身能力分类 二、linux系统的终端设备 1、 控制台 2、 伪终端pty(pseudo-tty) 3、 串口终端(/dev/ttySn) 4、 其它类型终端 三、...

    本文转载于:对于Linux内核tty设备的一点理解


    目录

    前言

    一、终端按照其自身能力分类

    二、linux系统的终端设备

    1、 控制台

    2、 伪终端pty(pseudo-tty)

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

    4、 其它类型终端

    三、内核文档翻译

    四、对于TTY系统的理解(图解)


    前言

    tty一词源于Teletypes,或Teletypewriters,它是最早出现的一种终端设备,类似电传打字机,由Teletype公司生产。最初tty是指连接到Unix系统上的物理或者虚拟终端。终端是一种字符型设备,通常使用tty来统称各种类型的终端设备。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备。它还有多种类,例如串口(ttySn、ttySACn、ttyOn)、USB到串口的转换器(ttyUSBn),还有需要特殊处理才能正常工作的调制解调器(比如传统的WinModem类设备)等。tty虚拟设备支持虚拟控制台,它能通过键盘及网络连接或者通过xterm会话登录到计算机上。

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

         对此还可以结合内核启动代码中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连接的传统计算机终端,不对数据进行本地处理或本地执行用户程序的串行通信终端。哑终端有时也指功能有限,只有单色文本处理能力或直接传输每一个键入的字符而不等待主机轮询的公共计算机终端。

    2、智能终端(胖客户端)

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


    二、linux系统的终端设备

    Linux系统的终端设备一般有以下几种:

    • 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,一般串口都这么做。

     

    • 当前控制台: /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 ,都会直接显示在当前的终端中。

    虚拟控制台 /dev/ttyn

    /dev/ttyn是进程虚拟控制台,他们共享同一个真实的物理控制台。

    如果在进程里打开一个这样的文件且该文件不是其他进程的控制台时,那该文件就是这个进程的控制台。进程printf数据会输出到这里。在PC上,用户可以使用alt+Fn切换控制台,看起来感觉存在多个屏幕,这种虚拟控制台对应tty1~n,其中 :

    /dev/tty1等代表第一个虚拟控制台

    例如当使用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-tty)

     

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

    历史上,有两套伪终端软件接口:

    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接口:使用一个/dev/ptmx作为master设备,在每次打开操作时会得到一个master设备fd,并在/dev/pts/目录下得到一个slave设备(如 /dev/pts/3和/dev/ptmx),这样就避免了逐个尝试的麻烦。由于可能有好几千个用户登陆,所以/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程序就通过“伪终端”进行通信。

     

    1. telnet<--->/dev/ptmx(master)<--->pts/*(slave)<--->getty

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

           通过使用适当的软件,就可以把两个甚至多个伪终端设备连接到同一个物理串行端

    1. 实验:
      1. 1、在X下打开一个或N个终端窗口
      2. 2、#ls /dev/pts/*
      3. 3、关闭这个X下的终端窗口,再次运行;比较两次输出信息就明白了。
      4. 输出为/dev/ptmx /dev/pts/1存在一(master)对多(slave)的情况
    • 3、 串口终端(/dev/ttySn)

            串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。有段时间串行端口设备通常被称为终端设备,那时它的最大用途就是用来连接终端,所以这些串行端口所对应的设备名称是/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以后的内核中,部分三星芯片(例如S3C24x0等)将串口终端设备节点命名为ttySACn。TI的Omap系列芯片从2.6.37开始芯片自带的UART设备开始使用专有的的omap-uart驱动,故设备节点命名为ttyOn,以区别于使用8250驱动时的设备名“ttySn”。

     

    • 4、 其它类型终端

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



    三、内核文档翻译

    其实在理解以上概念的时候,如果了解终端的发展历程,就可以比较容易理解tty、终端的概念。所以请大家阅读最后推荐的wiki英文网页,有助于理解上面的概念。当然,内核文档也是必不可少的参考资料,我顺手翻译了一下。

    内核文档/Documentation/devices.txt翻译节选:

    **** 终端设备

    Terminal, or TTY devices are a special class of character devices. A

    terminal device is any device that could act as a controlling terminal

    for a session; this includes virtual consoles, serial ports, and

    pseudoterminals .

    终端或这TTY设备是一类特殊的字符设备

    一个终端设备是任何对于一个会话可以作为控制终端的设备。

    这包括虚拟控制台、串口和伪终端(PTYs)。

    All terminal devices share a common set of capabilities known as line

    disciplines; these include the common terminal line discipline as well

    as SLIP and PPP modes.

    所有终端设备共享一系列常规能力-线路规程。

    这包含常见的终端线路规程,例如SLIP和PPP模式。

    All terminal devices are named similarly; this section explains the

    naming and use of the various types of TTYs. Note that the naming

    conventions include several historical warts; some of these are

    Linux-specific, some were inherited from other systems, and some

    reflect Linux outgrowing a borrowed convention.

    所有终端设备的命名都比较简单。本节介绍不同类型TTY的命名和用途。

    注意命名的约定包含了一些历史需求:

    某些是Linux特定的,

    某些是从其他的系统中继承下来的,

    还有一些则反映了Linux从借鉴来的约定中发展而来的。

    A hash mark (#) in a device name is used here to indicate a decimal

    number without leading zeroes.

    设备名中的(#)标志用于标识一个不以0开头的10进制数。

    Virtual consoles and the console device

    虚拟控制台和控制台设备

    Virtual consoles are full-screen terminal displays on the system video

    monitor. Virtual consoles are named /dev/tty#, with numbering

    starting at /dev/tty1; /dev/tty0 is the current virtual console.

    /dev/tty0 is the device that should be used to access the system video

    card on those architectures for which the frame buffer devices

    (/dev/fb*) are not applicable. Do not use /dev/console

    for this purpose.

    虚拟控制台是在系统视频监视器全屏的显示终端。

    虚拟控制台设备名为/dev/tty#,编号开始于/dev/tty1。

    /dev/tty0是当前虚拟控制台。

    /dev/tty0在那些帧缓冲设备(/dev/fb*)不适用的构架下可以被用来访问系统显卡。

    而/dev/console并不用于此目的。

    The console device, /dev/console, is the device to which system

    messages should be sent, and on which logins should be permitted in

    single-user mode. Starting with Linux 2.1.71, /dev/console is managed

    by the kernel; for previous versions it should be a symbolic link to

    either /dev/tty0, a specific virtual console such as /dev/tty1, or to

    a serial port primary (tty*, not cu*) device, depending on the

    configuration of the system.

    控制台设备/dev/console是一个接受系统信息并在单用户模式下允许登录的设备。

    从Linux 2.1.71开始,/dev/console由内核管理

    而以前的版本是一个到/dev/tty0、一个特定的虚拟控制台(如/dev/tty1)或者一个串口主(tty*,非cu*)设备动态链接,这些依赖系统配置。

    Serial ports

    串行端口

    Serial ports are RS-232 serial ports and any device which simulates

    one, either in hardware (such as internal modems) or in software (such

    as the ISDN driver.) Under Linux, each serial ports has two device

    names, the primary or callin device and the alternate or callout one.

    Each kind of device is indicated by a different letter. For any

    letter X, the names of the devices are /dev/ttyX# and /dev/cux#,

    respectively; for historical reasons, /dev/ttyS# and /dev/ttyC#

    correspond to /dev/cua# and /dev/cub#. In the future, it should be

    expected that multiple letters will be used; all letters will be upper

    case for the "tty" device (e.g. /dev/ttyDP#) and lower case for the

    "cu" device (e.g. /dev/cudp#).

    The names /dev/ttyQ# and /dev/cuq# are reserved for local use.

    名字(/dev/ttyQ#和/dev/cuq#)保留,用于本地使用。

    The alternate devices provide for kernel-based exclusion and somewhat

    different defaults than the primary devices. Their main purpose is to

    allow the use of serial ports with programs with no inherent or broken

    support for serial ports. Their use is deprecated, and they may be

    removed from a future version of Linux.

    备用设备提供基于内核的exclusion和某些与主要设备不同的默认配置。他们的主要目的是允许那些对于串口并非内部支持或是有一定问题的程序使用串口。他们的使用已经过时,他们可能会从未来的Linux版本中删除。

    Arbitration of serial ports is provided by the use of lock files with

    the names /var/lock/LCK..ttyX#. The contents of the lock file should

    be the PID of the locking process as an ASCII number.

    串口的仲裁是通过锁文件(/var/lock/LCK..ttyX#)来提供的。

    锁文件的内容应该是锁定进程PID的ASCII码。

    It is common practice to install links such as /dev/modem

    which point to serial ports. In order to ensure proper locking in the

    presence of these links, it is recommended that software chase

    symlinks and lock all possible names; additionally, it is recommended

    that a lock file be installed with the corresponding alternate

    device. In order to avoid deadlocks, it is recommended that the locks

    are acquired in the following order, and released in the reverse:

    安装一个例如/dev/modem的链接来指向串口是常见的做法。

    为了确保适当锁定在这些环节的存在,建议软件追踪符号并锁定所有可能的名字;

    此外,建议为相应的备用设备安装一个锁文件。

    为了避免死锁,建议按以下顺序获取锁,并按反向的顺序释放:

    1. The symbolic link name, if any (/var/lock/LCK..modem)

    2. The "tty" name (/var/lock/LCK..ttyS2)

    3. The alternate device name (/var/lock/LCK..cua2)

    1、符号链接名,如果有(/var/lock/LCK..modem)

    2、“tty”名(/var/lock/LCK..ttyS2)

    3、备用设备名(/var/lock/LCK..cua2)

    In the case of nested symbolic links, the lock files should be

    installed in the order the symlinks are resolved.

    在符号链接嵌套的情况下,锁定文件应按照符号链接的顺序来安装以解决问题。

    Under no circumstances should an application hold a lock while waiting

    for another to be released. In addition, applications which attempt

    to create lock files for the corresponding alternate device names

    should take into account the possibility of being used on a non-serial

    port TTY, for which no alternate device would exist.

    在任何情况下,应用程序应该等待另一个程序释放锁后,持有这个锁。

    此外,试图为相应的备用设备名创建锁文件的应用程序应考虑被用于非串口的TTY端口的可能性,此时没有备用设备存在。

    Pseudoterminals (PTYs)

    伪终端(PTYs)

    Pseudoterminals, or PTYs, are used to create login sessions or provide

    other capabilities requiring a TTY line discipline (including SLIP or

    PPP capability) to arbitrary data-generation processes. Each PTY has

    a master side, named /dev/pty[p-za-e][0-9a-f], and a slave side, named

    /dev/tty[p-za-e][0-9a-f]. The kernel arbitrates the use of PTYs by

    allowing each master side to be opened only once.

    伪终端(或PTYs)用于创建登录会话或提供给其他需要tty线路规程(包括SLIP或PPP能力)能力以生成数据的进程。

    每个PTY有一个主端(/dev/pty[p-za-e][0-9a-f])和一个从端(/dev/tty[p-za-e][0-9a-f])。

    内核通过只允许每个主端仅允许打开一次来仲裁PTY的使用。

    Once the master side has been opened, the corresponding slave device

    can be used in the same manner as any TTY device. The master and

    slave devices are connected by the kernel, generating the equivalent

    of a bidirectional pipe with TTY capabilities.

    一旦主端被打开,相应的从设备可以像任何TTY设备一样的方式被使用。

    主从设备都和内核连接,产生相当于一个带TTY功能的双向管道。

    Recent versions of the Linux kernels and GNU libc contain support for

    the System V/Unix98 naming scheme for PTYs, which assigns a common

    device, /dev/ptmx, to all the masters (opening it will automatically

    give you a previously unassigned PTY) and a subdirectory, /dev/pts,

    for the slaves; the slaves are named with decimal integers (/dev/pts/#

    in our notation). This removes the problem of exhausting the

    namespace and enables the kernel to automatically create the device

    nodes for the slaves on demand using the "devpts" filesystem.

    Linux内核的最近版本和GNU库包含了对于System V和Unix98对PTY命名方式的支持。

    它分配一个共用的设备(/dev/ptmx)给所有的主端(打开它会自动给你一个以前未分配的PTY)和一个子目录(/dev/pts)用于从端;从端通过十进制整数(/dev/pts/#)命名。

    这消除了命名空间枯竭的问题,并使内核通过“devpts”文件系统按需自动为从端动创建设备节点。


    四、对于TTY系统的理解(图解)

     

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    推荐阅读:《Linux C编程一站式学习》----第 34 章 终端、作业控制与守护进程---1. 终端

    wiki百科关于终端的网页:Computer terminal | System console | Linux console

     

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    以上是我参考了网上的资料后对tty的认识整理,参考资料如下:

    linux tty pty pts 概念 区别

    终端 /dev/console /dev/tty tty

    终端tty、虚拟控制台、FrameBuffer的切换过程详解

    LINUX下的tty,console与串口分析

    linux下tty,控制台,虚拟终端,串口,console(控制台终端)详解

    LINXU下的TTY、CONSOLE、串口

    Linux下的console和terminal

    展开全文
  • Linux内核tty设备

    千次阅读 2012-05-29 20:27:47
    虽然做嵌入式Linux,宿主机和开发板通信天天都在用tty设备,但是其实自己对TTY设备的认识几乎是0。对于Linux内核的终端、tty、控制台等概念的认识也很模糊。由于在学习的时候碰到了重定向console的问题,所以借机...
    虽然做嵌入式Linux,宿主机和开发板通信天天都在用tty设备,但是其实自己对TTY设备的认识几乎是0。对于Linux内核的终端、tty、控制台等概念的认识也很模糊。由于在学习的时候碰到了重定向console的问题,所以借机学习下tty的知识。以下是我对tty的认识总结,信息来源于网络和内核文档。参考资料见文章末尾。
    

    tty一词源于Teletypes,或Teletypewriters,它是最早出现的一种终端设备,类似电传打字机,由Teletype公司生产。最初tty是指连接到Unix系统上的物理或者虚拟终端。终端是一种字符型设备,通常使用tty来统称各种类型的终端设备。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备。它还有多种类,例如串口(ttySn、ttySACn、ttyOn)、USB到串口的转换器(ttyUSBn),还有需要特殊处理才能正常工作的调制解调器(比如传统的WinModem类设备)等。tty虚拟设备支持虚拟控制台,它能通过键盘及网络连接或者通过xterm会话登录到计算机上。


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

    4.     而个人计算机只有控制台,没有终端。当然愿意的话,可以在串口上连一两台字符哑终端。但是linux按POSIX标准把个人计算机当成小型机来用,在控制台上通过getty软件虚拟了六个字符哑终端(或者叫虚拟控制台终端tty1-tty6)(数量可以在/etc/inittab里自己调整)和一个图型终端, 在虚拟图形终端中又可以通过软件(如rxvt)再虚拟无限多个伪终端(pts/0等)。但这全是虚拟的,虽然用起来一样,但实际上没有物理实体。所以在个人计算机上,只有一个实际的控制台,没有终端,所有终端都是在控制台上用软件模拟的。要把个人计算机当主机再通过串口或网卡外连真正的物理终端也可以,论成本,谁会怎么做呢。

    终端设备文件一般有以下几种:

    • 1、 当前控制台: /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 ,都会直接显示在当前的终端中。

     

    • 2、 虚拟控制台 /dev/ttyn

    /dev/ttyn是进程虚拟控制台,如果在进程里打开一个这样的文件且该文件不是其他进程的控制台时,那该文件就是这个进程的控制台。进程printf数据会输出到这里。在PC上,用户可以使用alt+Fn切换控制台,看起来感觉存在多个屏幕,这种虚拟控制台对应tty1~n,其中 :

    /dev/tty1等代表第一个虚拟控制

    例如当使用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访问显卡。

    • 3、 /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,一般串口都这么做。

    • 4、 伪终端pty(pseudo-tty)

    伪终端(Pseudo Terminal)是终端的发展,为满足现在需求(比如网络登陆、xwindow窗口的管理)。它是成对出现的逻辑终端设备(即master和slave设备, 对master的操作会反映到slave上)。例如/dev/ptyp3和/dev/ttyp3(或有些文件系统中是 /dev/pty/m3和/dev/pty/s3,或 /dev/pts/3和/dev/ptmx)。它多用于模拟终端程序,是远程登陆(telnet、ssh、xterm等)后创建的控制台设备。

    现在大多数系统是通过pts/ptmx实现pty:pts(pseudo-terminal slave)是pty的实现方法,与ptmx(pseudo-terminal master)配合实现pty。

    例如,我们在X Window下打开的终端或使用telnet 或ssh等方式登录Linux主机,此时均通过pty设备(准确的说应该是pty从设备)。

    例如,如果某人在网上使用telnet程序连接到你的计算机上,则telnet程序就可能会开始连接到设备ptyp2(m2)上(一个伪终端端口上)。此时一个getty程序就应该运行在对应的ttyp2(s2)端口上。当telnet从远端获取了一个字符时,该字符就会通过m2、s2传递给 getty程序,而getty程序就会通过s2、m2和telnet程序往网络上返回“login:”字符串信息。这样,登录程序与telnet程序就通过“伪终端”进行通信。

     telnet---> ttyp3(S3: slave) --->ptyp3(M3: master) ---> getty


    在使用设备文件系统 (device filesystem)之前,为了得到大量的伪终端设备特殊文件,使用了比较复杂的文件名命名方式。因为只存在16个ttyp(ttyp0—ttypf) 的设备文件,为了得到更多的逻辑设备对,就使用了象q、r、s等字符来代替p。例如,ttys8和ptys8就是一个伪终端设备对。

    但Linux系统上并不使用上述方法,而使用了“pty master”方式,如/dev/ptm3。它的对应端则会被自动地创建成/dev/pts/3。这样就可以在需要时提供一个pty伪终端。目录 /dev/pts是一个类型为devpts的文件系统,并且可以在被加载文件系统列表中看到。虽然“文件”/dev/pts/3看上去是设备文件系统中的一项,但其实它完全是一种不同的文件系统。

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

    如果一个程序把ttyp3看作是一个串行端口设备,则它对该端口的读/写操作会反映在该逻辑终端设备对的另一个上面(ptyp3), 而ptyp3则是另一个程序用于读写操作的逻辑设备。这样,两个程序就可以通过这种逻辑设备进行互相交流,而其中一个使用ttyp3的程序则认为自己正在与一个串行端口进行通信。这很象是逻辑设备对之间的管道操作。

    对于ttyp3(s3),任何设计成使用一个串行端口设备的程序都可以使用该逻辑设备。但对于使用ptyp3的程序,则需要专门设计来使用ptyp3(m3)逻辑设备。

    通过使用适当的软件,就可以把两个甚至多个伪终端设备连接到同一个物理串行端口上。


    1. 实验:
    2. 1、在X下打开一个或N个终端窗口
    3. 2、#ls /dev/pts/*
    4. 3、关闭这个X下的终端窗口,再次运行;比较两次输出信息就明白了。
    5. 输出为/dev/ptmx /dev/pts/1存在一(master)对多(slave)的情况


    • 5、 串行端口终端(/dev/ttySn)

    串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。有段时间串行端口设备通常被称为终端设备,那时它的最大用途就是用来连接终端,所以这些串行端口所对应的设备名称是/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以后的内核中,部分三星芯片(例如S3C24x0等)将串口终端设备节点命名为ttySACn。TI的Omap系列芯片从2.6.37开始芯片自带的UART设备开始使用专有的的omap-uart驱动,故设备节点命名为ttyOn,以区别于使用8250驱动时的设备名“ttySn”。

    • 6、 其它类型终端

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

    内核文档/Documentation/devices.txt翻译节选:

    1. **** 终端设备
    2. Terminal, or TTY devices are a special class of character devices. A
    3. terminal device is any device that could act as a controlling terminal
    4. for a session; this includes virtual consoles, serial ports, and
    5. pseudoterminals .
    6. 终端或这TTY设备是一类特殊的字符设备
    7. 一个终端设备是任何对于一个会话可以作为控制终端的设备。
    8. 这包括虚拟控制台、串口和伪终端(PTYs)。
    9. All terminal devices share a common set of capabilities known as line
    10. disciplines; these include the common terminal line discipline as well
    11. as SLIP and PPP modes.
    12. 所有终端设备共享一系列常规能力-线路规程。
    13. 这包含常见的终端线路规程,例如SLIP和PPP模式。
    14. All terminal devices are named similarly; this section explains the
    15. naming and use of the various types of TTYs. Note that the naming
    16. conventions include several historical warts; some of these are
    17. Linux-specific, some were inherited from other systems, and some
    18. reflect Linux outgrowing a borrowed convention.
    19. 所有终端设备的命名都比较简单。本节介绍不同类型TTY的命名和用途。
    20. 注意命名的约定包含了一些历史需求:
    21. 某些是Linux特定的,
    22. 某些是从其他的系统中继承下来的,
    23. 还有一些则反映了Linux从借鉴来的约定中发展而来的。
    24. A hash mark (#) in a device name is used here to indicate a decimal
    25. number without leading zeroes.
    26. 设备名中的(#)标志用于标识一个不以0开头的10进制数。
    • Virtual consoles and the console device
    • 虚拟控制台和控制台设备
    1. Virtual consoles are full-screen terminal displays on the system video
    2. monitor. Virtual consoles are named /dev/tty#, with numbering
    3. starting at /dev/tty1; /dev/tty0 is the current virtual console.
    4. /dev/tty0 is the device that should be used to access the system video
    5. card on those architectures for which the frame buffer devices
    6. (/dev/fb*) are not applicable. Do not use /dev/console
    7. for this purpose.
    8. 虚拟控制台是在系统视频监视器全屏的显示终端。
    9. 虚拟控制台设备名为/dev/tty#,编号开始于/dev/tty1。
    10. /dev/tty0是当前虚拟控制台。
    11. /dev/tty0在那些帧缓冲设备(/dev/fb*)不适用的构架下可以被用来访问系统显卡。
    12. 而/dev/console并不用于此目的。
    13. The console device, /dev/console, is the device to which system
    14. messages should be sent, and on which logins should be permitted in
    15. single-user mode. Starting with Linux 2.1.71, /dev/console is managed
    16. by the kernel; for previous versions it should be a symbolic link to
    17. either /dev/tty0, a specific virtual console such as /dev/tty1, or to
    18. a serial port primary (tty*, not cu*) device, depending on the
    19. configuration of the system.
    20. 控制台设备/dev/console是一个接受系统信息并在单用户模式下允许登录的设备。
    21. 从Linux 2.1.71开始,/dev/console由内核管理
    22. 而以前的版本是一个到/dev/tty0、一个特定的虚拟控制台(如/dev/tty1)或者一个串口主(tty*,非cu*)设备动态链接,这些依赖系统配置。
    • Serial ports
    • 串行端口
    1. Serial ports are RS-232 serial ports and any device which simulates
    2. one, either in hardware (such as internal modems) or in software (such
    3. as the ISDN driver.) Under Linux, each serial ports has two device
    4. names, the primary or callin device and the alternate or callout one.
    5. Each kind of device is indicated by a different letter. For any
    6. letter X, the names of the devices are /dev/ttyX# and /dev/cux#,
    7. respectively; for historical reasons, /dev/ttyS# and /dev/ttyC#
    8. correspond to /dev/cua# and /dev/cub#. In the future, it should be
    9. expected that multiple letters will be used; all letters will be upper
    10. case for the "tty" device (e.g. /dev/ttyDP#) and lower case for the
    11. "cu" device (e.g. /dev/cudp#).
    12. 串行端口是RS-232串口和任何类似的设备,无论是硬件的(如内部调制解调器)或者软件(如ISDN驱动)。
    13. 在Linux下,每个串口有两个设备名,主要的(callin设备)和备用的(callout设备),每类设备都通过不同的字母标识。对于任何字母X,设备名分别是/dev/ttyX# 和/dev/cux#;由于历史原因,/dev/ttyS#和/dev/ttyC#对应于/dev/cua#和/dev/cub#。未来,对于“tty”多字母的名字将会被使用,所有的字母都将是大写(如/dev/ttyDP#),对于"cu"设备则使用小写字母(如/dev/cudp#)。
    14. The names /dev/ttyQ# and /dev/cuq# are reserved for local use.
    15. 名字(/dev/ttyQ#和/dev/cuq#)保留,用于本地使用。
    16. The alternate devices provide for kernel-based exclusion and somewhat
    17. different defaults than the primary devices. Their main purpose is to
    18. allow the use of serial ports with programs with no inherent or broken
    19. support for serial ports. Their use is deprecated, and they may be
    20. removed from a future version of Linux.
    21. 备用设备提供基于内核的exclusion和某些与主要设备不同的默认配置。他们的主要目的是允许那些对于串口并非内部支持或是有一定问题的程序使用串口。他们的使用已经过时,他们可能会从未来的Linux版本中删除。
    22. Arbitration of serial ports is provided by the use of lock files with
    23. the names /var/lock/LCK..ttyX#. The contents of the lock file should
    24. be the PID of the locking process as an ASCII number.
    25. 串口的仲裁是通过锁文件(/var/lock/LCK..ttyX#)来提供的。
    26. 锁文件的内容应该是锁定进程PID的ASCII码。
    27. It is common practice to install links such as /dev/modem
    28. which point to serial ports. In order to ensure proper locking in the
    29. presence of these links, it is recommended that software chase
    30. symlinks and lock all possible names; additionally, it is recommended
    31. that a lock file be installed with the corresponding alternate
    32. device. In order to avoid deadlocks, it is recommended that the locks
    33. are acquired in the following order, and released in the reverse:
    34. 安装一个例如/dev/modem的链接来指向串口是常见的做法。
    35. 为了确保适当锁定在这些环节的存在,建议软件追踪符号并锁定所有可能的名字;
    36. 此外,建议为相应的备用设备安装一个锁文件。
    37. 为了避免死锁,建议按以下顺序获取锁,并按反向的顺序释放:
    38. 1. The symbolic link name, if any (/var/lock/LCK..modem)
    39. 2. The "tty" name (/var/lock/LCK..ttyS2)
    40. 3. The alternate device name (/var/lock/LCK..cua2)
    41. 1、符号链接名,如果有(/var/lock/LCK..modem)
    42. 2、“tty”名(/var/lock/LCK..ttyS2)
    43. 3、备用设备名(/var/lock/LCK..cua2)
    44. In the case of nested symbolic links, the lock files should be
    45. installed in the order the symlinks are resolved.
    46. 在符号链接嵌套的情况下,锁定文件应按照符号链接的顺序来安装以解决问题。
    47. Under no circumstances should an application hold a lock while waiting
    48. for another to be released. In addition, applications which attempt
    49. to create lock files for the corresponding alternate device names
    50. should take into account the possibility of being used on a non-serial
    51. port TTY, for which no alternate device would exist.
    52. 在任何情况下,应用程序应该等待另一个程序释放锁后,持有这个锁。
    53. 此外,试图为相应的备用设备名创建锁文件的应用程序应考虑被用于非串口的TTY端口的可能性,此时没有备用设备存在。
    • Pseudoterminals (PTYs)
    • 伪终端(PTYs)
    1. Pseudoterminals, or PTYs, are used to create login sessions or provide
    2. other capabilities requiring a TTY line discipline (including SLIP or
    3. PPP capability) to arbitrary data-generation processes. Each PTY has
    4. a master side, named /dev/pty[p-za-e][0-9a-f], and a slave side, named
    5. /dev/tty[p-za-e][0-9a-f]. The kernel arbitrates the use of PTYs by
    6. allowing each master side to be opened only once.
    7. 伪终端(或PTYs)用于创建登录会话或提供给其他需要tty线路规程(包括SLIP或PPP能力)能力以生成数据的进程。
    8. 每个PTY有一个主端(/dev/pty[p-za-e][0-9a-f])和一个从端(/dev/tty[p-za-e][0-9a-f])。
    9. 内核通过只允许每个主端仅允许打开一次来仲裁PTY的使用。
    10. Once the master side has been opened, the corresponding slave device
    11. can be used in the same manner as any TTY device. The master and
    12. slave devices are connected by the kernel, generating the equivalent
    13. of a bidirectional pipe with TTY capabilities.
    14. 一旦主端被打开,相应的从设备可以像任何TTY设备一样的方式被使用。
    15. 主从设备都和内核连接,产生相当于一个带TTY功能的双向管道。
    16. Recent versions of the Linux kernels and GNU libc contain support for
    17. the System V/Unix98 naming scheme for PTYs, which assigns a common
    18. device, /dev/ptmx, to all the masters (opening it will automatically
    19. give you a previously unassigned PTY) and a subdirectory, /dev/pts,
    20. for the slaves; the slaves are named with decimal integers (/dev/pts/#
    21. in our notation). This removes the problem of exhausting the
    22. namespace and enables the kernel to automatically create the device
    23. nodes for the slaves on demand using the "devpts" filesystem.
    24. Linux内核的最近版本和GNU库包含了对于System V和Unix98对PTY命名方式的支持。
    25. 它分配一个共用的设备(/dev/ptmx)给所有的主端(打开它会自动给你一个以前未分配的PTY)和一个子目录(/dev/pts)用于从端;从端通过十进制整数(/dev/pts/#)命名。
    26. 这消除了命名空间枯竭的问题,并使内核通过“devpts”文件系统按需自动为从端动创建设备节点。
    对于TTY系统的理解(图解):

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    以上是我参考了网上的资料后对tty的认识整理,参考资料如下:

    linux tty pty pts 概念 区别

    终端 /dev/console /dev/tty tty

    终端tty、虚拟控制台、FrameBuffer的切换过程详解

    LINUX下的tty,console与串口分析

    linux下tty,控制台,虚拟终端,串口,console(控制台终端)详解

    LINXU下的TTY、CONSOLE、串口

    展开全文
  • Linux终端tty设备驱动

    千次阅读 2012-06-26 10:27:57
    Linux终端tty设备驱动 在Linux系统中,终端设备非常重要,没有终端设备,系统将无法向用户反馈信息,Linux中包含控制台、串口和伪终端3类终端设备。 14.1节阐述了终端设备的概念及分类,14.2节给出了Linux...

    在Linux系统中,终端设备非常重要,没有终端设备,系统将无法向用户反馈信息,Linux中包含控制台、串口和伪终端3类终端设备。
    14.1节阐述了终端设备的概念及分类,14.2节给出了Linux终端设备驱动的框架结构,重点描述tty_driver结构体及其成员。14.3~14.5节在14.2节的基础上,分别给出了Linux终端设备驱动模块加载/卸载函数和open()、close()函数,数据读写流程及tty设备线路设置的编程方法。在Linux中,串口驱动完全遵循tty驱动的框架结构,但是进行了底层操作的再次封装,14.6节描述了Linux针对串口tty驱动的这一封装,14.7节则具体给出了串口tty驱动的实现方法。14.8节基于14.6和14.7节的讲解给出了串口tty驱动的设计实例,即S3C2410集成UART的驱动。

    14.1终端设备
      
    在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司生产的。Linux中包含如下几类终端设备:
    1、串行端口终端(/dev/ttySn)
        串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。这些串行端口所对应的设备名称是 /dev/ttyS0(或/dev/tts/0)、/dev/ttyS1(或/dev/tts/1)等,设备号分别是(4,0)、(4,1)等。
        在命令行上把标准输出重定向到端口对应的设备文件名上就可以通过该端口发送数据,例如,在命令行提示符下键入: echo test > /dev/ttyS1会把单词“test”发送到连接在ttyS1端口的设备上。
    2.伪终端(/dev/pty/)
       
    伪终端(Pseudo Terminal)是成对的逻辑终端设备,并存在成对的设备文件,如/dev/ptyp3和/dev/ttyp3,它们与实际物理设备并不直接相关。如果一个程序把ttyp3看作是一个串行端口设备,则它对该端口的读/写操作会反映在该逻辑终端设备对应的ptyp3上,而ptyp3则是另一个程序用于读写操作的逻辑设备。这样,两个程序就可以通过这种逻辑设备进行互相交流,使用ttyp3的程序会认为自己正在与一个串行端口进行通信。
        以telnet 为例,如果某人在使用telnet程序连接到Linux系统,则telnet程序就可能会开始连接到设备ptyp2上,而此时一个getty程序会运行在对应的ttyp2端口上。当telnet从远端获取了一个字符时,该字符就会通过ptyp2、ttyp2传递给 getty程序,而getty程序则会通过ttyp2、ptyp2和telnet程序返回“login:”字符串信息。这样,登录程序与telnet程序 就通过伪终端进行通信。通过使用适当的软件,可以把2个或多个伪终端设备连接到同一个物理串行端口上。
    3.控制台终端(/dev/ttyn, /dev/console)
       
    如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。可以使用命令“ps –ax”来查看进程与哪个控制终端相连使用命令“tty”可以查看它具体对应哪个实际终端设备。/dev/tty有些类似于到实际所使用终端设备的一个联接。
        在UNIX系统中,计算机显示器通常被称为控制台终端(Console)。它仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等。当用户在控制台上登录时,使用的是tty1。使用Alt+[F1—F6]组合键时,我们就可以切换到tty2、tty3等上面去。tty1–tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,系统所产生的信息会发送到该终端上。因此不管当前正在使用哪个虚拟终端,系统信息都会发送到控制台终端上。用户可以登录到不同的虚拟终端上去,因而可以让系统同时有几个不同的会话期存在。只有系统或超级用户root可以向/dev/tty0进行写操作。
    在Linux 中,可以在系统启动命令行里指定当前的输出终端,格式如下:
    console=device, options
    device指代的是终端设备,可以是tty0(前台的虚拟终端)、ttyX(第X个虚拟终端)、ttySX(第X个串口)、lp0(第一个并口)等。options指代对device进行的设置,它取决于具体的设备驱动。对于串口设备,参数用来定义为:波特率、校验位、位数,格式为BBBBPN,其中BBBB表示波特率,P表示校验(n/o/e),N表示位数,默认options是9600n8。
        用户可以在内核命令行中同时设定多个终端,这样输出将会在所有的终端上显示,而当用户调用open()打开/dev/console时,最后一个终端将会返回作为当前值。例如:
    console=ttyS1, 9600 console=tty0
    定义了2个终端,而调用open()打开/dev/console时,将使用虚拟终端tty0。但是内核消息会在tty0 VGA虚拟终端和串口ttyS1上同时显示。
        通过查看/proc/tty/drivers文件可以获知什么类型的tty设备存在以及什么驱动被加载到内核,这个文件包括一个当前存在的不同 tty 驱动的列表,包括驱动名、缺省的节点名、驱动的主编号、这个驱动使用的次编号范围,以及 tty 驱动的类型。例如,下面给出了一个/proc/tty/drivers文件的例子:
     
    14.2终端设备驱动结构
       
    Linux内核中 tty的层次结构如图14.1所示,包含tty核心、tty线路规程和tty驱动,tty 线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式,例如 PPP 和 Bluetooth。tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个 tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。接收数据的流程为: 从tty硬件接收到的数据向上交给tty驱动,进入tty线路规程驱动,再进入 tty 核心,在这里它被一个用户获取。尽管大多数时候tty核心和tty之间的数据传输会经历tty线路规程的转换,但是tty驱动与tty核心之间也可以直接传输数据。
                     <!--[if !vml]--><!--[endif]-->
                               图14.1 tty分层结构
        图14.2显示了与tty相关的主要源文件及数据的流向。tty_io.c定义了tty 设备通用的file_operations结构体并实现了接口函数tty_register_driver()用于注册tty设备,它会利用 fs/char_dev.c提供的接口函数注册字符设备,与具体设备对应的tty驱动将实现tty_driver结构体中的成员函数。同时 tty_io.c也提供了tty_register_ldisc()接口函数用于注册线路规程,n_tty.c文件则实现了tty_disc结构体中的成员。
                     <!--[if !vml]--><!--[endif]-->
    图14.2 tty主要源文件关系及数据流向
    从图14.2可以看出,特定tty设备驱动的主体工作是填充tty_driver结构体中的成员,实现其中的成员函数,tty_driver结构体的定义如代码清单14.1。
    代码清单14.1 tty_driver结构体
    1  struct tty_driver                                                            
    2  {                                                                            
    3    int magic;                                                                 
    4    struct cdev cdev; /* 对应的字符设备cdev */                                                          
    5    struct module *owner;   /*这个驱动的模块拥有者 */                                                   
    6    const char *driver_name;                                                   
    7    const char *devfs_name;                                                    
    8    const char *name;   /* 设备名 */                                                       
    9    int name_base; /* offset of printed name */                    
    10   int major; /* 主设备号 */                                                  
    11   int minor_start; /* 开始次设备号 */                     
    12   int minor_num; /* 设备数量 */                                              
    13   int num; /* 被分配的设备数量 */                                            
    14   short type; /* tty驱动的类型 */                        
    15   short subtype; /* tty驱动的子类型 */             
    16   struct termios init_termios; /* 初始线路设置 */             
    17   int flags; /* tty驱动标志 */                          
    18   int refcount; /*引用计数(针对可加载的tty驱动) */                   
    19   struct proc_dir_entry *proc_entry; /* /proc文件系统入口 */         
    20   struct tty_driver *other; /* 仅对PTY驱动有意义 */          
    21   ...                                                                        
    22   /* 接口函数 */                                                             
    23   int(*open)(struct tty_struct *tty, struct file *filp);      
    24   void(*close)(struct tty_struct *tty, struct file *filp);     
    25   int(*write)(struct tty_struct *tty, const unsigned char *buf, int count);  
    26   void(*put_char)(struct tty_struct *tty, unsigned char ch);       
    27   void(*flush_chars)(struct tty_struct *tty);                    
    28   int(*write_room)(struct tty_struct *tty);                        
    29   int(*chars_in_buffer)(struct tty_struct *tty);                  
    30   int(*ioctl)(struct tty_struct *tty, struct file *file, unsigned int cmd,   
    31     unsigned long arg);                                                      
    32   void(*set_termios)(struct tty_struct *tty, struct termios *old);    
    33   void(*throttle)(struct tty_struct *tty);                       
    34   void(*unthrottle)(struct tty_struct *tty);                   
    35   void(*stop)(struct tty_struct *tty);                            
    36   void(*start)(struct tty_struct *tty);                     
    37   void(*hangup)(struct tty_struct *tty);               
    38   void(*break_ctl)(struct tty_struct *tty, int state);                   
    39   void(*flush_buffer)(struct tty_struct *tty);                       
    40   void(*set_ldisc)(struct tty_struct *tty);                     
    41   void(*wait_until_sent)(struct tty_struct *tty, int timeout);        
    42   void(*send_xchar)(struct tty_struct *tty, char ch);                  
    43   int(*read_proc)(char *page, char **start, off_t off, int count, int *eof,  
    44     void *data);                                                             
    45   int(*write_proc)(struct file *file, const char __user *buffer, unsigned long
    46     count, void *data);                                                      
    47   int(*tiocmget)(struct tty_struct *tty, struct file *file);           
    48   int(*tiocmset)(struct tty_struct *tty, struct file *file, unsigned int set,
    49     unsigned int clear);                                                     
    50                                                                              
    51   struct list_head tty_drivers;                          
    52 };   
        tty_driver结构体中的magic表示给这个结构体的“幻数”,设为 TTY_DRIVER_MAGIC,在 alloc_tty_driver()函数中被初始化。name与driver_name的不同在于后者表示驱动的名字,用在 /proc/tty 和 sysfs中,而前者表示驱动的设备节点名。type 与subtype描述tty驱动的类型和子类型,subtype的值依赖于type,type成员的可能值为 TTY_DRIVER_TYPE_SYSTEM(由tty子系统内部使用,subtype 应当设为 SYSTEM_TYPE_TTY、SYSTEM_TYEP_CONSOLE、SYSTEM_TYPE_SYSCONS或 SYSTEM_TYPE_SYSPTMX,这个类型不应当被任何常规tty驱动使用)、TTY_DRIVER_TYPE_CONSOLE(仅被控制台驱动使用)、TTY_DRIVER_TYPE_SERIAL(被任何串行类型驱动使用,subtype 应当设为 SERIAL_TYPE_NORMAL 或SERIAL_TYPE_CALLOUT)、TTY_DRIVER_TYPE_PTY(被伪控制台接口pty使用,此时subtype需要被设置为 PTY_TYPE_MASTER 或 PTY_TYPE_SLAVE)。init_termios 为初始线路设置,为一个termios结构体,这个成员被用来提供一个线路设置集合。termios 用于保存当前的线路设置,这些线路设置控制当前波特率、数据大小、数据流控设置等,这个结构体包含tcflag_t c_iflag(输入模式标志)、tcflag_t c_oflag(输出模式标志)、tcflag_t c_cflag(控制模式标志)、tcflag_t c_lflag(本地模式标志)、cc_t c_line(线路规程类型)、cc_t c_cc[NCCS](一个控制字符数组)等成员。驱动会使用一个标准的数值集初始化这个成员,它拷贝自tty_std_termios变量,tty_std_termos在tty核心中的定义如代码清单14.2。
    代码清单14.2 tty_std_termios变量
    1  struct termios tty_std_termios =
    2  {
    3   .c_iflag = ICRNL | IXON, /* 输入模式 */
    4   .c_oflag = OPOST | ONLCR, /* 输出模式 */
    5   .c_cflag = B38400 | CS8 | CREAD | HUPCL, /* 控制模式 */
    6   .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
    7   ECHOCTL | ECHOKE | IEXTEN,  /* 本地模式 */
    8   .c_cc = INIT_C_CC  /* 控制字符,用来修改终端的特殊字符映射 */
    9  };
         tty_driver 结构体中的major、minor_start、minor_num表示主设备号、次设备号及可能的次设备数,name表示设备名(如ttyS),第 23~49行的函数指针实际和tty_operations结构体等同,它们通常需在特定设备tty驱动模块初始化函数中被赋值。put_char()为单字节写函数,当单个字节被写入设备时这个函数被 tty 核心调用,如果一个 tty 驱动没有定义这个函数,将使用count参数为1的write()函数。flush_chars()与wait_until_sent()函数都用于刷新数据到硬件。write_room()指示有多少缓冲区空闲,chars_in_buffer()指示缓冲区中包含的数据数。当 ioctl(2)在设备节点上被调用时,ioctl()函数将被 tty核心调用。当设备的 termios 设置被改变时,set_termios()函数将被tty核心调用。throttle ()、unthrottle()、stop()和start()为数据抑制函数,这些函数用来帮助控制 tty 核心的输入缓存。当 tty 核心的输入缓冲满时,throttle()函数将被调用,tty驱动试图通知设备不应当发送字符给它。当 tty 核心的输入缓冲已被清空时,unthrottle()函数将被调用暗示设备可以接收数据。stop()和start()函数非常像throttle()和 unthrottle()函数,但它们表示 tty 驱动应当停止发送数据给设备以及恢复发送数据。
         当 tty驱动挂起 tty设备时,hangup()函数被调用,在此函数中进行相关的硬件操作。当tty 驱动要在 RS-232 端口上打开或关闭线路的 BREAK 状态时,break_ctl()线路中断控制函数被调用。如果state状态设为-1,BREAK 状态打开,如果状态设为 0,BREAK 状态关闭。如果这个函数由 tty 驱动实现,而tty核心将处理TCSBRK、TCSBRKP、TIOCSBRK和 TIOCCBRK这些ioctl命令。flush_buffer()函数用于刷新缓冲区并丢弃任何剩下的数据。set_ldisc()函数用于设置线路规程,当 tty 核心改变tty驱动的线路规程时这个函数被调用,这个函数通常不需要被驱动定义。send_xchar()为X-类型字符发送函数,这个函数用来发送一个高优先级 XON 或者 XOFF 字符给 tty设备,要被发送的字符在第2个参数ch中指定。read_proc()和write_proc()为/proc 读和写函数。tiocmget()函数用于获得tty 设备的线路设置,对应的tiocmset()用于设置tty设备的线路设置,参数set和clear包含了要设置或者清除的线路设置。
        Linux内核提供了一组函数用于操作tty_driver结构体及tty设备,包括:
    •  分配tty驱动
    struct tty_driver *alloc_tty_driver(int lines);
    这个函数返回tty_driver指针,其参数为要分配的设备数量,line会被赋值给tty_driver的num成员,例如:
    xxx_tty_driver = alloc_tty_driver(XXX_TTY_MINORS);
    if (!xxx_tty_driver) //分配失败
     return -ENOMEM; 
    •  注册tty驱动
    int tty_register_driver(struct tty_driver *driver);
    注册tty驱动成功时返回0,参数为由alloc_tty_driver ()分配的tty_driver结构体指针,例如:
    retval = tty_register_driver(xxx_tty_driver);
    if (retval) //注册失败
    {
      printk(KERN_ERR "failed to register tiny tty driver");
      put_tty_driver(xxx_tty_driver);
      return retval;
    }
    •  注销tty驱动
    int tty_unregister_driver(struct tty_driver *driver);
    这个函数与tty_register_driver ()对应,tty驱动最终会调用上述函数注销tty_driver。
    •  注册tty设备
    void tty_register_device(struct tty_driver *driver, unsigned index,
        struct device *device);
    仅有tty_driver是不够的,驱动必须依附于设备,tty_register_device()函数用于注册关联于tty_driver的设备,index为设备的索引(范围是0~driver->num),如:
    for (i = 0; i < XXX_TTY_MINORS; ++i)
    tty_register_device(xxx_tty_driver, i, NULL);
    •  注销tty设备
    void tty_unregister_device(struct tty_driver *driver, unsigned index);
    上述函数与tty_register_device()对应,用于注销tty设备,其使用方法如:
    for (i = 0; i < XXX_TTY_MINORS; ++i)
    tty_unregister_device(xxx_tty_driver, i);
    •  设置tty驱动操作
    void tty_set_operations(struct tty_driver *driver, struct tty_operations *op);
    上述函数会将tty_operations结构体中的函数指针拷贝给tty_driver对应的函数指针,在具体的tty驱动中,通常会定义1个设备特定的 tty_operations,tty_operations的定义如代码清单14.3。tty_operations中的成员函数与 tty_driver中的同名成员函数意义完全一致,因此,这里不再赘述。
    代码清单14.3 tty_operations结构体
    1  struct tty_operations
    2  {
    3     int  (*open)(struct tty_struct * tty, struct file * filp);
    4     void (*close)(struct tty_struct * tty, struct file * filp);
    5     int  (*write)(struct tty_struct * tty,
    6                   const unsigned char *buf, int count);
    7     void (*put_char)(struct tty_struct *tty, unsigned char ch);
    8     void (*flush_chars)(struct tty_struct *tty);
    9     int  (*write_room)(struct tty_struct *tty);
    10    int  (*chars_in_buffer)(struct tty_struct *tty);
    11    int  (*ioctl)(struct tty_struct *tty, struct file * file,
    12                unsigned int cmd, unsigned long arg);
    13    void (*set_termios)(struct tty_struct *tty, struct termios * old);
    14    void (*throttle)(struct tty_struct * tty);
    15    void (*unthrottle)(struct tty_struct * tty);
    16    void (*stop)(struct tty_struct *tty);
    17    void (*start)(struct tty_struct *tty);
    18    void (*hangup)(struct tty_struct *tty);
    19    void (*break_ctl)(struct tty_struct *tty, int state);
    20    void (*flush_buffer)(struct tty_struct *tty);
    21    void (*set_ldisc)(struct tty_struct *tty);
    22    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
    23    void (*send_xchar)(struct tty_struct *tty, char ch);
    24    int (*read_proc)(char *page, char **start, off_t off,
    25                      int count, int *eof, void *data);
    26    int (*write_proc)(struct file *file, const char __user *buffer,
    27                      unsigned long count, void *data);
    28    int (*tiocmget)(struct tty_struct *tty, struct file *file);
    29    int (*tiocmset)(struct tty_struct *tty, struct file *file,
    30                         unsigned int set, unsigned int clear);
    31 };
        终端设备驱动都围绕tty_driver结构体而展开,一般而言,终端设备驱动应包含如下组成:
    •  终端设备驱动模块加载函数和卸载函数,完成注册和注销tty_driver,初始化和释放终端设备对应的tty_driver结构体成员及硬件资源。
    •  实现tty_operations结构体中的一系列成员函数,主要是实现open()、close()、write()、tiocmget()、tiocmset()等函数。

    14.3终端设备驱动初始化与释放
    14.3.1模块加载与卸载函数
        tty驱动的模块加载函数中通常需要分配、初始化tty_driver结构体并申请必要的硬件资源,代码清单14.4。tty驱动的模块卸载函数完成与模块加载函数完成相反的工作。
    代码清单14.4 终端设备驱动模块加载函数范例
    1  /* tty驱动模块加载函数 */
    2  static int __init xxx_init(void)
    3  {
    4    ...
    5    /* 分配tty_driver结构体 */
    6    xxx_tty_driver = alloc_tty_driver(XXX_PORTS);
    7    /* 初始化tty_driver结构体 */
    8    xxx_tty_driver->owner = THIS_MODULE;
    9    xxx_tty_driver->devfs_name = "tts/";
    10   xxx_tty_driver->name = "ttyS";
    11   xxx_tty_driver->major = TTY_MAJOR;
    12   xxx_tty_driver->minor_start = 64;
    13   xxx_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
    14   xxx_tty_driver->subtype = SERIAL_TYPE_NORMAL;
    15   xxx_tty_driver->init_termios = tty_std_termios;
    16   xxx_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    17   xxx_tty_driver->flags = TTY_DRIVER_REAL_RAW;
    18   tty_set_operations(xxx_tty_driver, &xxx_ops);
    19
    20   ret = tty_register_driver(xxx_tty_driver);
    21   if (ret)
    22   {
    23     printk(KERN_ERR "Couldn't register xxx serial driver\n");
    24     put_tty_driver(xxx_tty_driver);
    25     return ret;
    26   }
    27
    28   ...
    29   ret = request_irq(...); /* 硬件资源申请 */
    30   ...
    31 } 
    14.3.2打开与关闭函数
        当用户对tty驱动所分配的设备节点进行open()系统调用时,tty_driver中的open()成员函数将被tty核心调用。tty 驱动必须设置open()成员,否则,-ENODEV将被返回给调用open()的用户。open()成员函数的第1个参数为一个指向分配给这个设备的 tty_struct 结构体的指针,第2个参数为文件指针。
    tty_struct结构体被 tty核心用来保存当前tty端口的状态,它的大多数成员只被 tty核心使用。tty_struct中的几个重要成员如下:
    •  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等。
    •  ldisc为给 tty 设备的线路规程。
    •  write_wait、read_wait为给tty写/读函数的等待队列,tty驱动应当在合适的时机唤醒对应的等待队列。
    •  termios为指向 tty 设备的当前 termios 设置的指针。
    •  stopped:1指示是否停止tty设备,tty 驱动可以设置这个值;hw_stopped:1指示是否tty设备已经被停止,tty 驱动可以设置这个值;flow_stopped:1指示是否 tty 设备数据流停止。
    •  driver_data、disc_data为数据指针,用于存储tty驱动和线路规程的“私有”数据。
    驱动中可以定义1个设备相关的结构体,并在open()函数中将其赋值给tty_struct的driver_data成员,如代码清单14.5。
    代码清单14.5 在tty驱动打开函数中赋值tty_struct的driver_data成员
    1  /* 设备“私有”数据结构体 */
    2  struct xxx_tty
    3  {
    4    struct tty_struct *tty; /* tty_struct指针 */
    5    int open_count; /* 打开次数 */
    6    struct semaphore sem; /* 结构体锁定信号量 */
    7    int xmit_buf; /* 传输缓冲区 */
    8    ...
    9  }
    10
    11 /* 打开函数 */
    12 static int xxx_open(struct tty_struct *tty, struct file *file)
    13 {
    14   struct xxx_tty *xxx;
    15
    16   /* 分配xxx_tty */
    17   xxx = kmalloc(sizeof(*xxx), GFP_KERNEL);
    18   if (!xxx)
    19     return  - ENOMEM;
    20   /* 初始化xxx_tty中的成员 */
    21   init_MUTEX(&xxx->sem);
    22   xxx->open_count = 0;
    23   ...
    24   /* 让tty_struct中的driver_data指向xxx_tty */
    25   tty->driver_data = xxx;
    26   xxx->tty = tty;
    27   ...
    28   return 0;
    29 }
        在用户对前面使用 open()系统调用而创建的文件句柄进行close()系统调用时,tty_driver中的close()成员函数将被tty核心调用。

    14.4 数据发送和接收
       
    图14.3给出了终端设备数据发送和接收过程中的数据流以及函数调用关系。用户在有数据发送给终端设备时,通过“write()系统调用――tty核心――线路规程”的层层调用,最终调用tty_driver结构体中的write()函数完成发送。
        因为速度和tty硬件缓冲区容量的原因,不是所有的写程序要求的字符都可以在调用写函数时被发送,因此写函数应当返回能够发送给硬件的字节数以便用户程序检查是否所有的数据被真正写入。如果在 wirte()调用期间发生任何错误,一个负的错误码应当被返回。
                          <!--[if !vml]--><!--[endif]-->
                            图14.3 终端设备数据发送和接收过程中的数据流和函数调用关系
        tty_driver 的write()函数接受3个参数tty_struct、发送数据指针及要发送的字节数,一般首先会通过tty_struct的driver_data成员得到设备私有信息结构体,然后依次进行必要的硬件操作开始发送,代码清单14.6给出了tty_driver的write()函数范例。
    代码清单14.6 tty_driver结构体的write()成员函数范例
    1  static int xxx_write(struct tty_struct *tty, const unsigned char *buf, int count)
    2  {
    3    /* 获得tty设备私有数据 */
    4    struct xxx_tty *xxx = (struct xxx_tty*)tty->driver_data;
    5    ...
    6    /* 开始发送 */
    7    while (1)
    8    {
    9      local_irq_save(flags);
    10     c = min_t(int, count, min(SERIAL_XMIT_SIZE - xxx->xmit_cnt - 1,
    11       SERIAL_XMIT_SIZE - xxx->xmit_head));
    12     if (c <= 0)
    13     {
    14       local_irq_restore(flags);
    15       break;
    16     }
    17     //拷贝到发送缓冲区
    18     memcpy(xxx->xmit_buf + xxx->xmit_head, buf, c);
    19     xxx->xmit_head = (xxx->xmit_head + c) &(SERIAL_XMIT_SIZE - 1);
    20     xxx->xmit_cnt += c;
    21     local_irq_restore(flags);
    22
    23     buf += c;
    24     count -= c;
    25     total += c;
    26   }
    27
    28   if (xxx->xmit_cnt && !tty->stopped && !tty->hw_stopped)
    29   {
    30     start_xmit(xxx);//开始发送
    31   }
    32   return total; //返回发送的字节数
    33 }     
        当tty子系统自己需要发送数据到 tty 设备时,如果没有实现 put_char()函数,write()函数将被调用,此时传入的count参数为1,通过对代码清单14.7的分析即可获知。
    代码清单14.7 put_char()函数的write()替代
    1  int tty_register_driver(struct tty_driver *driver)
    2  {
    3    ...
    4    if (!driver->put_char)//没有定义put_char()函数
    5      driver->put_char = tty_default_put_char;
    6    ...
    7  }
    8  static void tty_default_put_char(struct tty_struct *tty, unsigned char ch)
    9  {
    10   tty->driver->write(tty, &ch, 1);//调用tty_driver.write()函数
    11 }
        读者朋友们可能注意到了,tty_driver结构体中没有提供 read()函数。因为发送是用户主动的,而接收即用户调read()则是读一片缓冲区中已放好的数据。tty 核心在一个称为 struct tty_flip_buffer 的结构体中缓冲数据直到它被用户请求。因为tty核心提供了缓冲逻辑,因此每个 tty 驱动并非一定要实现它自身的缓冲逻辑。
        tty驱动不必过于关心tty_flip_buffer 结构体的细节,如果其count字段大于或等于TTY_FLIPBUF_SIZE,这个flip缓冲区就需要被刷新到用户,刷新通过对 tty_flip_buffer_push()函数的调用来完成,代码清单代码清单14.8给出了范例。
    代码清单14.8 tty_flip_buffer_push()范例
    1 for (i = 0; i < data_size; ++i)
    2 {
    3   if (tty->flip.count >= TTY_FLIPBUF_SIZE)
    4      tty_flip_buffer_push(tty);//数据填满向上层“推”
    5   tty_insert_flip_char(tty, data[i], TTY_NORMAL);//把数据插入缓冲区
    6 }
    7 tty_flip_buffer_push(tty);
        从tty 驱动接收到字符通过tty_insert_flip_char()函数被插入到flip缓冲区。该函数的第1个参数是数据应当保存入的 tty_struct结构体,第 2 个参数是要保存的字符,第3个参数是应当为这个字符设置的标志,如果字符是一个接收到的常规字符,则设为TTY_NORMAL,如果是一个特殊类型的指示错误的字符,依据具体的错误类型,应当设为TTY_BREAK、 TTY_PARITY或TTY_OVERRUN。

    14.5 TTY线路设置
    14.5.1线路设置用户空间接口
     用户可用如下2种方式改变tty设备的线路设置或者获取当前线路设置:
     1、调用用户空间的termios库函数
         用户空间的应用程序需引用termios.h头文件,该头文件包含了终端设备的I/O接口,实际是由POSIX定义的标准方法。对终端设备操作模式的描述由termios结构体完成,从代码清单14.2可以看出,这个结构体包含c_iflag、c_oflag、c_cflag、c_lflag和c_cc []几个成员。
        termios的c_cflag主要包含如下位域信息:CSIZE(字长)、CSTOPB(2个停止位)、 PARENB(奇偶校验位使能)、PARODD (奇校验位,当PARENB被使能时)、 CREAD(字符接收使能,如果没有置位,仍然从端口接收字符,但这些字符都要被丢弃)、 CRTSCTS (如果被置位,使能CTS状态改变报告)、CLOCAL (如果没有置位,使能调制解调器状态改变报告)。
        termios的c_iflag主要包含如下位域信息:INPCK (使能帧和奇偶校验错误检查)、BRKINT(break将清除终端输入/输出队列,向该终端上前台的程序发出SIGINT信号)、 PARMRK (奇偶校验和帧错误被标记,在INPCK被设置且IGNPAR未被设置的情况下才有意义)、IGNPAR (忽略奇偶校验和帧错误)、IGNBRK (忽略break)。
        通过tcgetattr()、tcsetattr()函数即可完成对终端设备的操作模式的设置和获取,这2个函数的原型如下:
    int tcgetattr (int fd, struct termios *termios_p);
    int tcsetattr (int fd, int optional_actions, struct termios *termios_p);
    例如,Raw模式的线路设置为:
    • 非正规模式
    • 关闭回显
    • 禁止 CR 到 NL 的映射(ICRNL)、输入奇偶校验、输入第 8 位的截取(ISTRIP)以及输出流控制
    • 8位字符(CS8),奇偶校验被禁止
    • 禁止所有的输出处理
    • 每次一个字节 (c_cc [VMIN] = 1、c_cc [VTIME] = 0)
    则对应的对termios结构体的设置就为:
    termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                                       | INLCR | IGNCR | ICRNL | IXON);
    termios_p->c_oflag &= ~OPOST;
    termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    termios_p->c_cflag &= ~(CSIZE | PARENB);
    termios_p->c_cflag |= CS8;
    通过如下一组函数可完成输入/输出波特率的获取和设置:
    speed_t cfgetospeed (struct termios *termios_p); //获得输出波特率
    speed_t cfgetispeed (struct termios *termios_p); //获得输入波特率
    int cfsetospeed (struct termios *termios_p, speed_t speed); //设置输出波特率
    int cfsetispeed (struct termios *termios_p, speed_t speed); //设置输入波特率
    如下一组函数则完成线路控制:
    int tcdrain (int fd); //等待所有输出都被发送
    int tcflush (int fd, int queue_selector); //flush输入/输出缓存
    int tcflow (int fd, int action); // 对输入和输出流进行控制
    int tcsendbreak (int fd, int duration);//发送break
        tcflush函数刷清(抛弃)输入缓存(终端驱动程序已接收到,但用户程序尚未读取)或输出缓存(用户程序已经写,但驱动尚未发送),queue参数可取TCIFLUSH(刷清输入队列)、TCOFLUSH(刷清输出队列)或TCIOFLUSH(刷清输入、输出队列)。
        tcflow()对输入输出进行流控制,action参数可取TCOOFF(输出被挂起)、TCOON(重新启动以前被挂起的输出)、TCIOFF(发送1个STOP字符,使终端设备暂停发送数据)、TCION(发送1个START字符,使终端恢复发送数据)。
       tcsendbreak()函数在一个指定的时间区间内发送连续的0二进位流。若duration参数为0,则此种发送延续0.25-0.5秒之间。POSIX.1说明若duration非0,则发送时间依赖于实现。
    2、对tty设备节点进行ioctl()调用
         大部分termios库函数会被转化为对tty设备节点的ioctl()调用,例如tcgetattr()、tcsetattr()函数对应着TCGETS、TCSETS IO控制命令。TIOCMGET (获得MODEM状态位)、TIOCMSET(设置MODEM状态位)、TIOCMBIC       (清除指示MODEM位)、TIOCMBIS(设置指示MODEM位)这4个IO控制命令用于获取和设置MODEM握手,如RTS、CTS、DTR、 DSR、RI、CD等。
    14.5.2 tty驱动set_termios函数
        大部分 termios 用户空间函数被库转换为对驱动节点的 ioctl()调用,而tty ioctl中的大部分命令会被tty核心转换为对tty驱动的set_termios()函数的调用。set_termios()函数需要根据用户对 termios的设置(termios设置包括字长、奇偶校验位、停止位、波特率等)完成实际的硬件设置。
    tty_operations中的set_termios()函数原型为:
    void(*set_termios)(struct tty_struct *tty, struct termios *old);
        新的设置被保存在tty_struct中,旧的设置被保存在old参数中,若新旧参数相同,则什么都不需要做,对于被改变的设置,需完成硬件上的设置,代码清单14.9给出了set_termios()函数的例子。
    代码清单14.9 tty驱动程序set_termios()函数范例
    1  static void xxx_set_termios(struct tty_struct *tty, struct termios *old_termios)
    2  {
    3    struct xxx_tty *info = (struct cyclades_port*)tty->driver_data;
    4    /* 新设置等同于老设置,什么也不做 */
    5    if (tty->termios->c_cflag == old_termios->c_cflag)
    6      return ;
    7    ...

    9    /* 关闭CRTSCTS硬件流控制 */
    10   if ((old_termios->c_cflag &CRTSCTS) && !(cflag &CRTSCTS))
    11   {
    12     ...
    13   }
    14
    15   /* 打开CRTSCTS硬件流控制 */
    16   if (!(old_termios->c_cflag &CRTSCTS) && (cflag &CRTSCTS))
    17   {
    18     ...
    19   }
    20
    21   /* 设置字节大小 */
    22   switch (tty->termios->c_cflag &CSIZE)
    23   {
    24
    25     case CS5:
    26     ...
    27     case CS6:
    28     ...
    29     case CS7:
    30     ...
    31     case CS8:
    32     ...
    33   }
    34
    35   /* 设置奇偶校验 */
    36   if (tty->termios->c_cflag &PARENB)
    37     if (tty->termios->c_cflag &PARODD)  //奇校验
    38     ...
    39     else  //偶校验
    40     ...
    41   else //无校验
    42   ...
    43 }
    14.5.3 tty驱动 tiocmget和tiocmset函数
    对TIOCMGET、 TIOCMSET、TIOCMBIC和TIOCMBIS IO控制命令的调用将被tty核心转换为对tty驱动tiocmget()函数和tiocmset()函数的调用,TIOCMGET对应tiocmget ()函数,TIOCMSET、TIOCMBIC和TIOCMBIS 对应tiocmset()函数,分别用于读取Modem控制的设置和进行Modem的设置。代码清单14.10给出了tiocmget()函数的范例,代 码清单14.11则给出了tiocmset()函数的范例。
    代码清单14.10 tty驱动程序tiocmget()函数范例
    1  static int xxx_tiocmget(struct tty_struct *tty, struct file *file)
    2  {
    3    struct xxx_tty *info = tty->driver_ data;
    4    unsigned int result = 0;
    5    unsigned int msr = info->msr;
    6    unsigned int mcr = info->mcr;
    7    result = ((mcr &MCR_DTR) ? TIOCM_DTR : 0) |  /* DTR 被设置 */
    8    ((mcr &MCR_RTS) ? TIOCM_RTS : 0) |  /* RTS 被设置 */
    9    ((mcr &MCR_LOOP) ? TIOCM_LOOP : 0) |  /* LOOP 被设置 */
    10   ((msr &MSR_CTS) ? TIOCM_CTS : 0) |  /* CTS 被设置 */
    11   ((msr &MSR_CD) ? TIOCM_CAR : 0) |  /* CD 被设置*/
    12   ((msr &MSR_RI) ? TIOCM_RI : 0) |  /* 振铃指示被设置 */
    13   ((msr &MSR_DSR) ? TIOCM_DSR : 0); /* DSR 被设置 */
    14   return result;
    15 }
    代码清单14.11 tty驱动程序tiocmset()函数范例
    1  static int xxx_tiocmset(struct tty_struct *tty, struct file *file, unsigned
    2    int set, unsigned int clear)
    3  {
    4    struct xxx_tty *info = tty->driver_data;
    5    unsigned int mcr = info->mcr;

    7    if (set &TIOCM_RTS) /* 设置RTS */
    8      mcr |= MCR_RTS;
    9    if (set &TIOCM_DTR) /* 设置DTR */
    10     mcr |= MCR_RTS;
    11
    12   if (clear &TIOCM_RTS) /* 清除RTS */
    13     mcr &= ~MCR_RTS;
    14   if (clear &TIOCM_DTR) /* 清除DTR */
    15     mcr &= ~MCR_RTS;
    16
    17   /* 设置设备新的MCR值 */
    18   tiny->mcr = mcr;
    19   return 0;
    20 }
    tiocmget()函数会访问MODEM状态寄存器(MSR),而tiocmset()函数会访问MODEM控制寄存器(MCR)。
    14.5.3 tty驱动ioctl函数
        当用户在tty设备节点上进行ioctl(2) 调用时,tty_operations中的 ioctl()函数会被tty核心调用。如果 tty 驱动不知道如何处理传递给它的 ioctl 值,它返回 –ENOIOCTLCMD,之后tty 核心会执行一个通用的操作。
    驱动中常见的需处理 的IO控制命令包括TIOCSERGETLSR(获得这个 tty 设备的线路状态寄存器LSR 的值)、TIOCGSERIAL(获得串口线信息)、TIOCMIWAIT(等待 MSR 改变)、TIOCGICOUNT(获得中断计数)等。代码清单14.12给出了tty驱动程序ioctl()函数的范例。
    代码清单14.12 tty驱动程序ioctl()函数范例
    1  static int xxx_ioctl(struct tty_struct *tty, struct file *filp, unsigned int
    2    cmd, unsigned long arg)
    3  {
    4    struct xxx_tty *info = tty->driver_data;
    5    ...
    6    /* 处理各种命令 */
    7    switch (cmd)
    8    {
    9      case TIOCGSERIAL:
    10       ...
    11     case TIOCSSERIAL:
    12       ...
    13     case TIOCSERCONFIG:
    14       ...
    15     case TIOCMIWAIT:
    16     ...
    17     case TIOCGICOUNT:
    18     ...
    19     case TIOCSERGETLSR:
    20     ...
    21   }
    22   ...
    23 }

    14.6 UART设备驱动
       
    尽管一个特定的UART设备驱动完全可以遵循14.2~14.5的方法来设计,即定义tty_driver并实现其中的成员函数,但是Linux已经在文件 serial_core.c中实现了UART设备的通用tty驱动层(姑且称其为串口核心层),这样,UART驱动的主要任务演变成实现serial- core.c中定义的一组uart_xxx接口而非tty_xxx接口,如图14.5所示。
        serial_core.c串口核心层完全可以被当作14.2~14.5节tty设备驱动的实例,它实现了UART设备的tty驱动。
        提示:Linux驱动的这种分层思想在许多类型的设备驱动中都得到了体现,例如上一章IDE设备驱动中,内核实现了通用的IDE层用于处理块设备I/O请求,而具体的IDE则只需使用ide_xxx这样的接口,甚至不必理会复杂的块设备驱动结构。
                 <!--[if !vml]--><!--[endif]-->
                            图14.5 串口核心层
    串口核心层为串口设备驱动提供了如下3个结构体:
    1、uart_driver
    uart_driver包含串口设备的驱动名、设备名、设备号等信息,它封装了tty_driver,使得底层的UART驱动无需关心tty_driver,其定义如代码清单14.13。
    代码清单14.13 uart_driver结构体
    1  struct uart_driver
    2  {
    3    struct module *owner;
    4    const char *driver_name; //驱动名
    5    const char *dev_name;    //设备名
    6    const char *devfs_name;  //设备文件系统名
    7    int major;  //主设备号
    8    int minor;   //次设备号
    9    int nr;
    10   struct console *cons;
    11
    12   /* 私有的,底层驱动不应该访问这些成员,应该被初始化为NULL */
    13   struct uart_state *state;
    14   struct tty_driver *tty_driver;
    15 };
    一个tty驱动必须注册/注销tty_driver,而一个UART驱动则演变为注册/注销uart_driver,使用如下接口:
    int uart_register_driver(struct uart_driver *drv);
    void uart_unregister_driver(struct uart_driver *drv);
    实际上,uart_register_driver()和uart_unregister_driver()中分别包含了tty_register_driver()和tty_unregister_driver()的操作,如代码清单14.14所示。
    代码清单14.14 uart_register_driver()和uart_unregister_driver()函数
    1  int uart_register_driver(struct uart_driver *drv)
    2  {
    3   struct tty_driver *normal = NULL;
    4   int i, retval;
    5   ...
    6    /* 分配tty_driver */
    7   normal  = alloc_tty_driver(drv->nr);
    8   if (!normal)
    9    goto out;
    10  drv->tty_driver = normal;
    11   /* 初始化tty_driver */
    12  normal->owner  = drv->owner;
    13  normal->driver_name = drv->driver_name;
    14  normal->devfs_name = drv->devfs_name;
    15  normal->name  = drv->dev_name;
    16  normal->major  = drv->major;
    17  normal->minor_start = drv->minor;
    18  normal->type  = TTY_DRIVER_TYPE_SERIAL;
    19  normal->subtype  = SERIAL_TYPE_NORMAL;
    20  normal->init_termios = tty_std_termios;
    21  normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    22  normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
    23  normal->driver_state    = drv;
    24  tty_set_operations(normal, &uart_ops);
    25
    26  ...
    27  /* 注册tty驱动 */
    28  retval = tty_register_driver(normal);
    29  out:
    30  if (retval < 0) {
    31   put_tty_driver(normal);
    32   kfree(drv->state);
    33  }
    34  return retval;
    35 }
    36
    37 void uart_unregister_driver(struct uart_driver *drv)
    38 {
    39  struct tty_driver *p = drv->tty_driver;
    40  tty_unregister_driver(p);  /* 注销tty驱动 */
    41  put_tty_driver(p);
    42  kfree(drv->state);
    43  drv->tty_driver = NULL;
    44 }
    2、uart_port
    uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息,其定义如代码清单14.15。
    代码清单14.15 uart_port结构体
    1  struct uart_port
    2  {
    3    spinlock_t lock; /* 端口锁 */
    4    unsigned int iobase; /* IO端口基地址 */
    5    unsigned char __iomem *membase; /* IO内存基地址 */
    6    unsigned int irq; /* 中断号 */
    7    unsigned int uartclk; /* UART时钟 */
    8    unsigned char fifosize; /* 传输fifo大小 */
    9    unsigned char x_char; /* xon/xoff字符 */
    10   unsigned char regshift; /* 寄存器位移 */
    11   unsigned char iotype; /* IO存取类型 */
    12
    13   #define UPIO_PORT  (0)  /* IO端口*/
    14   #define UPIO_HUB6  (1)
    15   #define UPIO_MEM  (2)  /* IO内存*/
    16   #define UPIO_MEM32  (3)
    17   #define UPIO_AU   (4)   /* Au1x00类型IO */
    18
    19   unsigned int read_status_mask; /* 驱动相关的 */
    20   unsigned int ignore_status_mask; /* 驱动相关的 */
    21   struct uart_info *info; /* 指向parent信息 */
    22   struct uart_icount icount; /* 计数 */
    23
    24   struct console *cons; /* console结构体 */
    25   #ifdef CONFIG_SERIAL_CORE_CONSOLE
    26     unsigned long sysrq; /* sysrq超时 */
    27   #endif
    28
    29   upf_t flags;
    30
    31   #define UPF_FOURPORT  ((__force upf_t) (1 << 1))
    32   #define UPF_SAK   ((__force upf_t) (1 << 2))
    33   #define UPF_SPD_MASK  ((__force upf_t) (0x1030))
    34   #define UPF_SPD_HI  ((__force upf_t) (0x0010))
    35   #define UPF_SPD_VHI  ((__force upf_t) (0x0020))
    36   #define UPF_SPD_CUST  ((__force upf_t) (0x0030))
    37   #define UPF_SPD_SHI  ((__force upf_t) (0x1000))
    38   #define UPF_SPD_WARP  ((__force upf_t) (0x1010))
    39   #define UPF_SKIP_TEST  ((__force upf_t) (1 << 6))
    40   #define UPF_AUTO_IRQ  ((__force upf_t) (1 << 7))
    41   #define UPF_HARDPPS_CD  ((__force upf_t) (1 << 11))
    42   #define UPF_LOW_LATENCY  ((__force upf_t) (1 << 13))
    43   #define UPF_BUGGY_UART  ((__force upf_t) (1 << 14))
    44   #define UPF_MAGIC_MULTIPLIER ((__force upf_t) (1 << 16))
    45   #define UPF_CONS_FLOW  ((__force upf_t) (1 << 23))
    46   #define UPF_SHARE_IRQ  ((__force upf_t) (1 << 24))
    47   #define UPF_BOOT_AUTOCONF ((__force upf_t) (1 << 28))
    48   #define UPF_IOREMAP  ((__force upf_t) (1 << 31))
    49
    50   #define UPF_CHANGE_MASK  ((__force upf_t) (0x17fff))
    51   #define UPF_USR_MASK  ((__force upf_t)
    52       (UPF_SPD_MASK|UPF_LOW_LATENCY))
    53   unsigned int mctrl; /* 目前modem控制设置 */
    54   unsigned int timeout; /* 基于字符的超时 */
    55   unsigned int type; /* 端口类型 */
    56   const struct uart_ops *ops; /* UART操作集 */
    57   unsigned int custom_divisor;
    58   unsigned int line; /* 端口索引 */
    59   unsigned long mapbase; /* ioremap后基地址 */
    60   struct device *dev; /* parent设备 */
    61   unsigned char hub6;
    62   unsigned char unused[3];
    63 };
    串口核心层提供如下函数来添加1个端口:
    int uart_add_one_port(struct uart_driver *drv, struct uart_port *port);
        对上述函数的调用应该发生在uart_register_driver()之后,uart_add_one_port()的一个最重要作用是封装了tty_register_device()。
        uart_add_one_port()的“反函数”是uart_remove_one_port(),其中会调用tty_unregister_device(),原型为:
    int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port);
        驱动中虽然不需要处理uart_port的uart_info成员,但是在发送时,从用户来的数据被保存在xmit(被定义为circ_buf,即环形缓冲 区)中,因此UART驱动在发送数据时(一般在发送中断处理函数中),需要从这个circ_buf获取上层传递下来的字符。
    3、uart_ops
         uart_ops 定义了针对UART的一系列操作,包括发送、接收及线路设置等,如果说tty_driver中的tty_operations对于串口还较为抽象,那么 uart_ops则直接面向了串口的UART,其定义如代码清单14.16。Linux驱动的这种层次非常类似于面向对象编程中基类、派生类的关系,派生类针对特定的事物会更加具体,而基类则站在更高的抽象层次上。
    代码清单14.16 uart_ops结构体
    1  struct uart_ops
    2  {
    3    unsigned int(*tx_empty)(struct uart_port*);
    4    void(*set_mctrl)(struct uart_port *, unsigned int mctrl);
    5    unsigned int(*get_mctrl)(struct uart_port*);
    6    void(*stop_tx)(struct uart_port*);    //停止发送
    7    void(*start_tx)(struct uart_port*);   //开始发送
    8    void(*send_xchar)(struct uart_port *, char ch); //发送xchar
    9    void(*stop_rx)(struct uart_port*);   //停止接收
    10   void(*enable_ms)(struct uart_port*);
    11   void(*break_ctl)(struct uart_port *, int ctl);
    12   int(*startup)(struct uart_port*);
    13   void(*shutdown)(struct uart_port*);
    14   void(*set_termios)(struct uart_port *, struct termios *new, struct termios
    15     *old);        //设置termios
    16   void(*pm)(struct uart_port *, unsigned int state, unsigned int oldstate);
    17   int(*set_wake)(struct uart_port *, unsigned int state);
    18
    19   /* 返回1个描述端口类型的字符串 */
    20   const char *(*type)(struct uart_port*);
    21
    22   /* 释放端口使用的IO和内存资源,必要的情况下,应该进行iounmap操作 */
    23   void(*release_port)(struct uart_port*);
    24   /* 申请端口使用的IO和内存资源 */
    25   int(*request_port)(struct uart_port*);
    26  
    27   void(*config_port)(struct uart_port *, int);
    28   int(*verify_port)(struct uart_port *, struct serial_struct*);
    29   int(*ioctl)(struct uart_port *, unsigned int, unsigned long);
    30 };
        serial_core.c 中定义了tty_operations的实例,包含uart_open()、uart_close()、uart_write()、 uart_send_xchar()等成员函数(如代码清单14.17),这些函数会借助uart_ops结构体中的成员函数来完成具体的操作,代码清单 14.18给出了tty_operations的uart_send_xchar()成员函数利用uart_ops中start_tx()、 send_xchar()成员函数的例子。
    代码清单14.17 串口核心层的tty_operations实例
    1  static struct tty_operations uart_ops =
    2  {
    3   .open  = uart_open,//串口打开
    4   .close  = uart_close,//串口关闭
    5   .write  = uart_write,//串口发送
    6   .put_char = uart_put_char,//...
    7   .flush_chars = uart_flush_chars,
    8   .write_room = uart_write_room,
    9   .chars_in_buffer= uart_chars_in_buffer,
    10  .flush_buffer = uart_flush_buffer,
    11  .ioctl  = uart_ioctl,
    12  .throttle = uart_throttle,
    13  .unthrottle = uart_unthrottle,
    14  .send_xchar = uart_send_xchar,
    15  .set_termios = uart_set_termios,
    16  .stop  = uart_stop,
    17  .start  = uart_start,
    18  .hangup  = uart_hangup,
    19  .break_ctl = uart_break_ctl,
    20  .wait_until_sent= uart_wait_until_sent,
    21 #ifdef CONFIG_PROC_FS
    22  .read_proc = uart_read_proc, //proc入口读函数
    23 #endif
    24  .tiocmget = uart_tiocmget,
    25  .tiocmset = uart_tiocmset,
    26 };
    代码清单14.18 串口核心层的tty_operations与uart_ops关系
    1  static void uart_send_xchar(struct tty_struct *tty, char ch)
    2  {
    3    struct uart_state *state = tty->driver_data;
    4    struct uart_port *port = state->port;
    5    unsigned long flags;
    6    //如果uart_ops中实现了send_xchar成员函数
    7    if (port->ops->send_xchar)
    8      port->ops->send_xchar(port, ch); 
    9    else //uart_ops中未实现send_xchar成员函数
    10   {
    11     port->x_char = ch; //xchar赋值
    12     if (ch)
    13     {
    14       spin_lock_irqsave(&port->lock, flags);
    15       port->ops->start_tx(port);  //发送xchar
    16       spin_unlock_irqrestore(&port->lock, flags);
    17     }
    18   }
    19 }
    注意:  整个调用流程为:  系统调用write()->uart_write()(tty_driver)->port->ops->start_tx();
      在使用串口核心层这个通用串口tty驱动层的接口后,一个串口驱动要完成的主要工作将包括:
    •  定义uart_driver、uart_ops、uart_port等结构体的实例并在适当的地方根据具体硬件和驱动的情况初始化它们,当然具体设备 xxx的驱动可以将这些结构套在新定义的xxx_uart_driver、xxx_uart_ops、xxx_uart_port之内。
    •  在模块初始化时调用uart_register_driver()和uart_add_one_port()以注册UART驱动并添加端口,在模块卸载时 调用uart_unregister_driver()和uart_remove_one_port()以注销UART驱动并移除端口。
    •  根据具体硬件的datasheet实现uart_ops中的成员函数,这些函数的实现成为UART驱动的主体工作。

    14.7实例:S3C2410 UART的驱动
    14.7.1 S3C2410串口硬件描述
        S3C2410 内部具有3个独立的UART控制器,每个控制器都可以工作在Interrupt(中断)模式或DMA(直接内存访问)模式,也就是说UART控制器可以在 CPU与UART控制器传送资料的时候产生中断或DMA请求。S3C2410集成的每个UART均具有16字节的FIFO,支持的最高波特率可达到 230.4Kbps。
    ULCONn(UART Line Control Register)寄存器用于S3C2410 UART的线路控制,用于设置模式、每帧的数据位数、停止位数及奇偶校验,如表14.1。
    表14.1 S3C2410 UART的ULCONn寄存器
    ULCONn 位              描述
    保留 [7] 
    红外模式 [6]           0:正常模式  1:红外模式
    奇偶校验 [5:3]         0xx:无校验  100:奇校验  101:偶校验  ...
    停止位 [2]             0:1个停止位  1:2个停止位
    字长 [1:0]             00:5位 01:6位 10:7位 11:8位
    UCONn(UART Control Register)寄存器用于从整体上控制S3C2410 UART的中断模式及工作模式(DMA、中断、轮询)等,如表14.2。
    表14.2 S3C2410 UART的UCONn寄存器
    UCONn 位               描述
    时钟选择 [10]            为UART的波特率产生选择PCLK或UCLK时钟
    Tx中断 [9]               0:脉冲 1:电平
    Rx中断 [8]               0:脉冲 1:电平
    Rx超时使能 [7]          当UART被使能,使能/禁止Rx超时中断  0:禁止  1:使能
    Rx错误状态中断使能 [6]   使能接收异常中断(如break、帧错误、校验错、溢出等)
    loopback [5]              0:正常模式 1:回环
    发送break [4]            设置该位将造成UART在1帧的时间内发送break,当发送完break后,该位将自动被清除
    发送模式 [3:2]            发送数据到UART的模式,00:禁止 01:中断或轮询 10:DMA0(仅针对UART0)、DMA3(仅针对UART3) 11:DMA1(仅针对UART1)
    接收模式 [1:0]            从UART接收数据的模式,00:禁止 01:中断或轮询 10:DMA0(仅针对UART0)
         UFCONn(UART FIFO Conrtol Register)寄存器用于S3C2410 UART的FIFO控制,用于控制FIFO中断的触发级别以及复位时是否清空FIFO中的内容,如表14.3。
     表14.3 S3C2410 UART的UFCONn寄存器
    UFCONn 位              描述
    Tx FIFO触发级别 [7:6]    决定发送FIFO的触发级别: 00:空 01:4字节 10:8字节 11:12字节
    Rx FIFO触发级别 [5:4]    决定接收FIFO的触发级别: 00:4字节 01:8字节 10:12字节 11:16字节
    Tx FIFO复位 [2]          复位FIFO后自动清除FIFO  0:正常 1:Tx FIFO复位
    Rx FIFO复位 [1]          复位FIFO后自动清除FIFO  0:正常 1:Tx FIFO复位
    FIFO使能 [0]             0:禁止 1:使能
    代码清单14.19给出了UFCONn寄存器的位掩码和默认设置(使能FIFO、Tx FIFO为空时触发中断、Rx FIFO中包含8个字节时触发中断)。
    代码清单14.19 S3C2410 UART UFCONn寄存器的位掩码和默认设置
    1  #define S3C2410_UFCON_FIFOMODE   (1<<0)
    2  #define S3C2410_UFCON_TXTRIG0   (0<<6)
    3  #define S3C2410_UFCON_RXTRIG8   (1<<4)
    4  #define S3C2410_UFCON_RXTRIG12   (2<<4)

    6  #define S3C2410_UFCON_RESETBOTH   (3<<1)
    7  #define S3C2410_UFCON_RESETTX   (1<<2)
    8  #define S3C2410_UFCON_RESETRX   (1<<1)

    10 #define S3C2410_UFCON_DEFAULT   (S3C2410_UFCON_FIFOMODE | \
    11        S3C2410_UFCON_TXTRIG0  | \
    12        S3C2410_UFCON_RXTRIG8 )
    UFSTATn(UART FIFO Status Register)寄存器用于表征UART FIFO的状态,如表14.4。
    表14.4 S3C2410 UART的UFSTATn寄存器
    UFSTATn 位              描述
    保留 [15:10] 
    Tx FIFO满 [9]            当Tx FIFO满后,将自动被设置为1  0:0字节 ≤ Tx FIFO数据数 ≤ 15  1:Tx FIFO数据数 = 15
    Rx FIFO满 [8]            当Rx FIFO满后,将自动被设置为1  0:0字节 ≤ Rx FIFO数据数 ≤ 15  1:Tx FIFO数据数 = 15
    Tx FIFO数据数 [7:4] 
    Rx FIFO数据数 [3:0]
    由于UFSTATn寄存器中的Tx FIFO数据数和Rx FIFO数据数分别占据[7:4]和[3:0]位,因此定义S3C2410_UFSTAT_TXSHIFT和 S3C2410_UFSTAT_RXSHIFT分别为4和0,代码清单14.20给出了UFSTATn寄存器的位掩码等信息。
    代码清单14.20 S3C2410 UART UFSTATn寄存器的位掩码
    1 #define S3C2410_UFSTAT_TXFULL   (1<<9)
    2 #define S3C2410_UFSTAT_RXFULL   (1<<8)
    3 #define S3C2410_UFSTAT_TXMASK   (15<<4)
    4 #define S3C2410_UFSTAT_TXSHIFT   (4)
    5 #define S3C2410_UFSTAT_RXMASK   (15<<0)
    6 #define S3C2410_UFSTAT_RXSHIFT   (0)
    UTXHn(UART Transmit Buffer Register)和 URXHn(UART Receive Buffer Register)分别是UART发送和接收数据寄存器,这2个寄存器存放着发送和接收的数据。
    UTRSTATn(UART TX/RX Status Register)寄存器反映了发送和接收的状态,通过这个寄存器,驱动程序可以判断URXHn中是否有数据接收到或UTXHn是否为空,这个寄存器主要在非FIFO模式时使用。
    UMCONn(UART Modem Control Register)用于S3C2410 UART的modem控制,设置是否使用RTS流控,若采用流控,可选择自动流控(Auto Flow Control,AFC)或由软件控制RTS信号的“高”或“低”电平。
    14.7.2 S3C2410串口驱动数据结构
    S3C2410串口驱动中uart_driver结构体实例的定义如代码清单14.21,设备名为“s3c2410_serial”,驱动名为“ttySAC”。
    代码清单14.21 S3C2410串口驱动uart_driver结构体
    1  #define S3C24XX_SERIAL_NAME "ttySAC"
    2  #define S3C24XX_SERIAL_DEVFS    "tts/"
    3  #define S3C24XX_SERIAL_MAJOR 204
    4  #define S3C24XX_SERIAL_MINOR 64

    6  static struct uart_driver s3c24xx_uart_drv =
    7  {
    8   .owner  = THIS_MODULE,
    9   .dev_name = "s3c2410_serial",
    10  .nr  = 3,
    11  .cons  = S3C24XX_SERIAL_CONSOLE,
    12  .driver_name = S3C24XX_SERIAL_NAME,
    13  .devfs_name = S3C24XX_SERIAL_DEVFS,
    14  .major  = S3C24XX_SERIAL_MAJOR,
    15  .minor  = S3C24XX_SERIAL_MINOR,
    16 };
    S3C2410 串口驱动中定义了结构体s3c24xx_uart_port,该结构体中封装了uart_port结构体及一些针对S3C2410 UART的附加信息,代码清单14.22给出了s3c24xx_uart_port结构体及其实例s3c24xx_serial_ports[]数组。
    代码清单14.22 S3C2410串口驱动s3c24xx_uart_port结构体
    1  struct s3c24xx_uart_port
    2  {
    3   unsigned char   rx_claimed;
    4   unsigned char   tx_claimed;

    6   struct s3c24xx_uart_info *info;
    7   struct s3c24xx_uart_clksrc *clksrc;
    8   struct clk   *clk;
    9   struct clk   *baudclk;
    10  struct uart_port  port;
    11 };
    12
    13 static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = {
    14  [0] = {
    15   .port = {
    16    .lock  = SPIN_LOCK_UNLOCKED,
    17    .iotype  = UPIO_MEM,
    18    .irq  = IRQ_S3CUART_RX0,
    19    .uartclk = 0,
    20    .fifosize = 16,
    21    .ops  = &s3c24xx_serial_ops,
    22    .flags  = UPF_BOOT_AUTOCONF,
    23    .line  = 0,//端口索引:0
    24   }
    25  },
    26  [1] = {
    27   .port = {
    28    .lock  = SPIN_LOCK_UNLOCKED,
    29    .iotype  = UPIO_MEM,
    30    .irq  = IRQ_S3CUART_RX1,
    31    .uartclk = 0,
    32    .fifosize = 16,
    33    .ops  = &s3c24xx_serial_ops,
    34    .flags  = UPF_BOOT_AUTOCONF,
    35    .line  = 1, //端口索引:1
    36   }
    37  },
    38 #if NR_PORTS > 2
    39
    40  [2] = {
    41   .port = {
    42    .lock  = SPIN_LOCK_UNLOCKED,
    43    .iotype  = UPIO_MEM,
    44    .irq  = IRQ_S3CUART_RX2,
    45    .uartclk = 0,
    46    .fifosize = 16,
    47    .ops  = &s3c24xx_serial_ops,
    48    .flags  = UPF_BOOT_AUTOCONF,
    49    .line  = 2, //端口索引:2
    50   }
    51  }
    52 #endif
    53 };
    S3C2410串口驱动中uart_ops结构体实例的定义如代码清单14.23,将一系列s3c24xx_serial_函数赋值给了uart_ops结构体的成员。
    代码清单14.23 S3C2410串口驱动uart_ops结构体
    1  static struct uart_ops s3c24xx_serial_ops =
    2  {
    3   .pm  = s3c24xx_serial_pm,
    4   .tx_empty = s3c24xx_serial_tx_empty,//发送缓冲区空
    5   .get_mctrl = s3c24xx_serial_get_mctrl,//得到modem控制设置
    6   .set_mctrl = s3c24xx_serial_set_mctrl, //设置modem控制(MCR)
    7   .stop_tx = s3c24xx_serial_stop_tx, //停止接收字符
    8   .start_tx = s3c24xx_serial_start_tx,//开始传输字符
    9   .stop_rx = s3c24xx_serial_stop_rx, //停止接收字符
    10  .enable_ms = s3c24xx_serial_enable_ms,// modem状态中断使能
    11  .break_ctl = s3c24xx_serial_break_ctl,// 控制break信号的传输
    12  .startup = s3c24xx_serial_startup,//启动端口
    13  .shutdown = s3c24xx_serial_shutdown,// 禁用端口
    14  .set_termios = s3c24xx_serial_set_termios,//改变端口参数
    15  .type  = s3c24xx_serial_type,//返回描述特定端口的常量字符串指针
    16  .release_port = s3c24xx_serial_release_port,//释放端口占用的内存及IO资源
    17  .request_port = s3c24xx_serial_request_port,//申请端口所需的内存和IO资源
    18  .config_port = s3c24xx_serial_config_port,//执行端口所需的自动配置步骤
    19  .verify_port = s3c24xx_serial_verify_port,//验证新的串行端口信息
    20 };
    set_mctrl()函数的原型为:
    void (*set_mctrl)(struct uart_port *port, u_int mctrl);
    它将参数port所对应的调制解调器控制线的值设为参数mctrl的值。
    get_mctrl()函数的原型为:
    unsigned int (*get_mctrl)(struct uart_port *port);
    该函数返回调制解调器控制输入的现有状态,这些状态信息包括:TIOCM_CD(CD 信号状态)、TIOCM_CTS(CTS信号状态)、TIOCM_DSR(DSR信号状态)、TIOCM_RI(RI信号状态)等。如果信号被置为有效,则对应位将被置位。
    端口启动函数startup()的原型为:
    int (*startup)(struct uart_port *port, struct uart_info *info);
    该函数申请所有中断资源,初始化底层驱动状态,并开启端口为可接收数据的状态。
    shutdown()函数完成与startup()函数的作用相反,其原型:
    void (*shutdown)(struct uart_port *port, struct uart_info *info);
    这个函数禁用端口,释放所有的中断资源。
    回过头来看s3c24xx_uart_port结构体,其中的s3c24xx_uart_info成员(代码清单14.22第6行)是一些针对S3C2410 UART的信息,其定义如代码清单14.24。
    代码清单14.24 S3C2410串口驱动s3c24xx_uart_info结构体
    1  static struct s3c24xx_uart_info s3c2410_uart_inf =
    2  {
    3   .name  = "Samsung S3C2410 UART",
    4   .type  = PORT_S3C2410,
    5   .fifosize = 16,
    6   .rx_fifomask = S3C2410_UFSTAT_RXMASK,
    7   .rx_fifoshift = S3C2410_UFSTAT_RXSHIFT,
    8   .rx_fifofull = S3C2410_UFSTAT_RXFULL,
    9   .tx_fifofull = S3C2410_UFSTAT_TXFULL,
    10  .tx_fifomask = S3C2410_UFSTAT_TXMASK,
    11  .tx_fifoshift = S3C2410_UFSTAT_TXSHIFT,
    12  .get_clksrc = s3c2410_serial_getsource,
    13  .set_clksrc = s3c2410_serial_setsource,
    14  .reset_port = s3c2410_serial_resetport,
    15 };
    在S3C2410串口驱动中,针对UART的设置(UCONn、ULCONn、UFCONn寄存器等)被封装到s3c2410_uartcfg结构体中,其定义如代码清单14.25。
    代码清单14.25 S3C2410串口驱动s3c2410_uartcfg结构体
    1  struct s3c2410_uartcfg
    2  {
    3    unsigned char hwport; /* 硬件端口号 */
    4    unsigned char unused;
    5    unsigned short flags;
    6    unsigned long uart_flags; /* 缺省的uart标志 */

    8    unsigned long ucon; /* 端口的ucon值 */
    9    unsigned long ulcon; /* 端口的ulcon值 */
    10   unsigned long ufcon; /* 端口的ufcon值 */
    11
    12   struct s3c24xx_uart_clksrc *clocks;
    13   unsigned int clocks_size;
    14 };
    14.7.3 S3C2410串口驱动初始化与释放
        在S3C2410 串口驱动的模块加载函数中会调用uart_register_driver()注册s3c24xx_uart_drv这个uart_driver,同时经过s3c2410_serial_init()→s3c24xx_serial_init()→platform_driver_register()的调用导致s3c24xx_serial_probe()被执行,而s3c24xx_serial_probe()函数中会调用 s3c24xx_serial_init_port()初始化UART端口并调用uart_add_one_port()添加端口,整个过程的对应代码如 清单14.26。
    代码清单14.26 S3C2410串口驱动初始化过程
    1   static int __init s3c24xx_serial_modinit(void)
    2   {
    3    int ret;
    4     //注册uart_driver
    5    ret = uart_register_driver(&s3c24xx_uart_drv);
    6    if (ret < 0) {
    7     printk(KERN_ERR "failed to register UART driver\n");
    8     return -1;
    9    }
    10    //初始化s3c2410的串口
    11   s3c2410_serial_init();
    12 
    13   return 0;
    14  }
    15 
    16  static inline int s3c2410_serial_init(void)
    17  {
    18   return s3c24xx_serial_init(&s3c2410_serial_drv, &s3c2410_uart_inf);
    19  }
    20 
    21  static int s3c24xx_serial_init(struct platform_driver *drv,
    22            struct s3c24xx_uart_info *info)
    23  {
    24   dbg("s3c24xx_serial_init(%p,%p)\n", drv, info);
    25   return platform_driver_register(drv);//注册平台驱动
    26  }
    27 
    28  //平台驱动probe()函数
    29  static int s3c24xx_serial_probe(struct platform_device *dev,
    30      struct s3c24xx_uart_info *info)
    31  {
    32   struct s3c24xx_uart_port *ourport;
    33   int ret;
    34 
    35   dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index);
    36 
    37   ourport = &s3c24xx_serial_ports[probe_index];
    38   probe_index++;
    39 
    40   dbg("%s: initialising port %p...\n", __FUNCTION__, ourport);
    41    //初始化uart端口
    42   ret = s3c24xx_serial_init_port(ourport, info, dev);
    43   if (ret < 0)
    44    goto probe_err;
    45 
    46   dbg("%s: adding port\n", __FUNCTION__);
    47   //添加uart_port
    48   uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
    49   platform_set_drvdata(dev, &ourport->port);
    50 
    51   return 0;
    52 
    53   probe_err:
    54   return ret;
    55  }
    56  //42行调用的s3c24xx_serial_init_port()函数
    57  static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
    58          struct s3c24xx_uart_info *info,
    59          struct platform_device *platdev)
    60  {
    61   struct uart_port *port = &ourport->port;
    62   struct s3c2410_uartcfg *cfg;
    63   struct resource *res;
    64 
    65   dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev);
    66 
    67   if (platdev == NULL)
    68    return -ENODEV;
    69 
    70   cfg = s3c24xx_dev_to_cfg(&platdev->dev);
    71 
    72   if (port->mapbase != 0)
    73    return 0;
    74 
    75   if (cfg->hwport > 3)
    76    return -EINVAL;
    77 
    78   /* 为端口设置info成员 */
    79   port->dev = &platdev->dev;
    80   ourport->info = info;
    81 
    82   /* 初始化fifosize */
    83   ourport->port.fifosize = info->fifosize;
    84 
    85   dbg("s3c24xx_serial_init_port: %p (hw %d)...\n", port, cfg->hwport);
    86 
    87   port->uartclk = 1;
    88    /* 如果使用流控 */
    89   if (cfg->uart_flags & UPF_CONS_FLOW) {
    90    dbg("s3c24xx_serial_init_port: enabling flow control\n");
    91    port->flags |= UPF_CONS_FLOW;
    92   }
    93 
    94   /* 利用平台资源中记录的信息初始化uart端口的基地址、中断号 */
    95   res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
    96   if (res == NULL) {
    97    printk(KERN_ERR "failed to find memory resource for uart\n");
    98    return -EINVAL;
    99   }
    100
    101  dbg("resource %p (%lx..%lx)\n", res, res->start, res->end);
    102
    103  port->mapbase = res->start;
    104  port->membase = S3C24XX_VA_UART+(res->start - S3C24XX_PA_UART);
    105  port->irq = platform_get_irq(platdev, 0);
    106
    107  ourport->clk = clk_get(&platdev->dev, "uart");
    108
    109  dbg("port: map=%08x, mem=%08x, irq=%d, clock=%ld\n",
    110      port->mapbase, port->membase, port->irq, port->uartclk);
    111
    112  /* 复位fifo并设置uart */
    113  s3c24xx_serial_resetport(port, cfg);
    114  return 0;
    115 }
    116 //113行调用的s3c24xx_serial_resetport()函数
    117 static inline int s3c24xx_serial_resetport(struct uart_port * port,
    118         struct s3c2410_uartcfg *cfg)
    119 {
    120  struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
    121
    122  return (info->reset_port)(port, cfg);
    123 }
    124 //122行调用的info->reset_port()函数
    125 static int s3c2410_serial_resetport(struct uart_port *port,
    126         struct s3c2410_uartcfg *cfg)
    127 {
    128  dbg("s3c2410_serial_resetport: port=%p (%08lx), cfg=%p\n",
    129      port, port->mapbase, cfg);
    130
    131  wr_regl(port, S3C2410_UCON,  cfg->ucon);
    132  wr_regl(port, S3C2410_ULCON, cfg->ulcon);
    133
    134  /* 复位2个fifo */
    135  wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
    136  wr_regl(port, S3C2410_UFCON, cfg->ufcon);
    137
    138  return 0;
    139 }
        在S3C2410串口驱动模块被卸载时,它会最终调用uart_remove_one_port()释放uart_port并调用uart_unregister_driver()注销uart_driver,如代码清单14.27所示。
    代码清单14.27 S3C2410串口驱动释放过程
    1  static void __exit s3c24xx_serial_modexit(void)
    2  {
    3   s3c2410_serial_exit();
    4    //注销uart_driver
    5   uart_unregister_driver(&s3c24xx_uart_drv);
    6  }

    8  static inline void s3c2410_serial_exit(void)
    9  {
    10  //注销平台驱动
    11  platform_driver_unregister(&s3c2410_serial_drv);
    12 }
    13
    14 static int s3c24xx_serial_remove(struct platform_device *dev)
    15 {
    16  struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
    17
    18  //移除uart端口
    19  if (port)
    20   uart_remove_one_port(&s3c24xx_uart_drv, port);
    21
    22  return 0;
    23 }
         上述代码中对S3C24xx_serial_remove()的调用发生在platform_driver_unregister()之后,由于S3C2410的UART是集成于SoC芯片内部的一个独立的硬件单元,因此也被作为1个平台设备而定义。
    14.7.4 S3C2410串口数据收发
         S3C2410串口驱动uart_ops结构体的startup ()成员函数s3c24xx_serial_startup()用于启动端口,申请端口的发送、接收中断,使能端口的发送和接收,其实现如代码清单14.28。
    代码清单14.28 S3C2410串口驱动startup ()函数
    1  static int s3c24xx_serial_startup(struct uart_port *port)
    2  {
    3   struct s3c24xx_uart_port *ourport = to_ourport(port);
    4   int ret;

    6   dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n",
    7       port->mapbase, port->membase);

    9   rx_enabled(port) = 1;//置接收使能状态为1
    10   //申请接收中断
    11  ret = request_irq(RX_IRQ(port),
    12      s3c24xx_serial_rx_chars, 0,
    13      s3c24xx_serial_portname(port), ourport);
    14
    15  if (ret != 0) {
    16   printk(KERN_ERR "cannot get irq %d\n", RX_IRQ(port));
    17   return ret;
    18  }
    19  
    20  ourport->rx_claimed = 1;
    21
    22  dbg("requesting tx irq...\n");
    23
    24  tx_enabled(port) = 1;//置发送使能状态为1
    25   //申请发送中断
    26  ret = request_irq(TX_IRQ(port),
    27      s3c24xx_serial_tx_chars, 0,
    28      s3c24xx_serial_portname(port), ourport);
    29
    30  if (ret) {
    31   printk(KERN_ERR "cannot get irq %d\n", TX_IRQ(port));
    32   goto err;
    33  }
    34
    35  ourport->tx_claimed = 1;
    36
    37  dbg("s3c24xx_serial_startup ok\n");
    38
    39  /* 端口复位代码应该已经为端口控制设置了正确的寄存器 */
    40
    41  return ret;
    42
    43  err:
    44  s3c24xx_serial_shutdown(port);
    45  return ret;
    46 }
    s3c24xx_serial_startup()的“反函数”为s3c24xx_serial_shutdown(),其释放中断,禁止发送和接收,实现如代码清单14.29。
    代码清单14.29 S3C2410串口驱动shutdown ()函数
    1  static void s3c24xx_serial_shutdown(struct uart_port *port)
    2  {
    3   struct s3c24xx_uart_port *ourport = to_ourport(port);

    5   if (ourport->tx_claimed) {
    6    free_irq(TX_IRQ(port), ourport);
    7    tx_enabled(port) = 0; //置发送使能状态为0
    8    ourport->tx_claimed = 0;
    9   }
    10
    11  if (ourport->rx_claimed) {
    12   free_irq(RX_IRQ(port), ourport);
    13   ourport->rx_claimed = 0;
    14   rx_enabled(port) = 0; //置接收使能状态为1
    15  }
    16 }
        S3C2410 串口驱动uart_ops结构体的tx_empty()成员函数s3c24xx_serial_tx_empty()用于判断发送缓冲区是否为空,其实现 如代码清单14.30,当使能FIFO模式的时候,判断UFSTATn寄存器,否则判断UTRSTATn寄存器的相应位。
    代码清单14.30 S3C2410串口驱动tx_empty()函数
    1  /* 检查发送缓冲区/FIFO是否为空 */
    2  static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port)
    3  {
    4    //fifo模式,检查UFSTATn寄存器
    5    struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
    6    unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT);
    7    unsigned long ufcon = rd_regl(port, S3C2410_UFCON);

    9    if (ufcon &S3C2410_UFCON_FIFOMODE)
    10   {
    11     if ((ufstat &info->tx_fifomask) != 0 ||  //Tx fifo数据数非0
    12     (ufstat &info->tx_fifofull))   // Tx fifo满
    13       return 0;   //0:非空
    14
    15     return 1; //1:空
    16   }
    17
    18   return s3c24xx_serial_txempty_nofifo(port);
    19 }
    20
    21 static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
    22 {
    23   //非fifo模式,检查UTRSTATn寄存器
    24   return (rd_regl(port, S3C2410_UTRSTAT) &S3C2410_UTRSTAT_TXE);
    25 }
        S3C2410 串口驱动uart_ops结构体的start_tx ()成员函数s3c24xx_serial_start_tx()用于启动发送,而stop_rx()成员函数 s3c24xx_serial_stop_rx()用于停止发送,代码清单14.31给出了这2个函数的实现。
    代码清单14.31 S3C2410串口驱动start_tx ()、stop_rx()函数
    1  static void s3c24xx_serial_start_tx(struct uart_port *port)
    2  {
    3   if (!tx_enabled(port)) {//如果端口发送未使能
    4    if (port->flags & UPF_CONS_FLOW)
    5     s3c24xx_serial_rx_disable(port);

    7    enable_irq(TX_IRQ(port));//使能发送中断
    8    tx_enabled(port) = 1;//置端口发送使能状态为1
    9   }
    10 }
    11
    12 static void s3c24xx_serial_stop_tx(struct uart_port *port)
    13 {
    14  if (tx_enabled(port)) {//如果端口发送已使能
    15   disable_irq(TX_IRQ(port));//禁止发送中断
    16   tx_enabled(port) = 0;//置端口发送使能状态为0
    17   if (port->flags & UPF_CONS_FLOW)
    18    s3c24xx_serial_rx_enable(port);
    19  }
    20 }
         S3C2410 串口驱动uart_ops结构体的stop_rx ()成员函数s3c24xx_serial_stop_rx ()用于停止接收,代码清单14.32给出了这个函数的实现。注意uart_ops中没有start_rx()成员,因为接收并不是由“我方”启动,而是 由“它方”的发送触发“我方”的接收中断,“我方”再被动响应。
    代码清单14.32 S3C2410串口驱动stop_rx ()函数
    1  static void s3c24xx_serial_stop_rx(struct uart_port *port)
    2  {
    3   if (rx_enabled(port)) {//如果接收为使能
    4    dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
    5    disable_irq(RX_IRQ(port));//禁止接收中断
    6    rx_enabled(port) = 0;//置接收使能状态为0
    7   }
    8  }
        在S3C2410 串口驱动中,与数据收发关系最密切的函数不是上述uart_ops成员函数,而是s3c24xx_serial_startup()为发送和接收中断注册 的中断处理函数s3c24xx_serial_rx_chars()和s3c24xx_serial_tx_chars()。s3c24xx_serial_rx_chars ()读取URXHn寄存器以获得接收到的字符,并调用uart_insert_char()将该字符添加了tty设备的flip缓冲区中,当接收到64个 字符或者不再能接受到字符后,调用tty_flip_buffer_push()函数向上层“推”tty设备的flip缓冲,其实现如代码清单 14.33。
    代码清单14.33 S3C2410串口驱动接收中断处理函数
    1  static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id, struct
    2    pt_regs *regs)
    3  {
    4    struct s3c24xx_uart_port *ourport = dev_id;
    5    struct uart_port *port = &ourport->port; //获得uart_port
    6    struct tty_struct *tty = port->info->tty; //获得tty_struct
    7    unsigned int ufcon, ch, flag, ufstat, uerstat;
    8    int max_count = 64;

    10   while (max_count-- > 0)
    11   {
    12     ufcon = rd_regl(port, S3C2410_UFCON);
    13     ufstat = rd_regl(port, S3C2410_UFSTAT);
    14     //如果接收到0个字符
    15     if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
    16       break;
    17
    18     uerstat = rd_regl(port, S3C2410_UERSTAT);
    19     ch = rd_regb(port, S3C2410_URXH); //读出字符
    20
    21     if (port->flags &UPF_CONS_FLOW)
    22     {
    23       int txe = s3c24xx_serial_txempty_nofifo(port);
    24
    25       if (rx_enabled(port)) //如果端口为使能接收状态
    26       {
    27         if (!txe) //如果发送缓冲区为空
    28         {
    29           rx_enabled(port) = 0; //置端口为使能接收状态为0
    30           continue;
    31         }
    32       }
    33       else   //端口为禁止接收状态
    34       {
    35         if (txe)  //如果发送缓冲区非空
    36         {
    37           ufcon |= S3C2410_UFCON_RESETRX;
    38           wr_regl(port, S3C2410_UFCON, ufcon);
    39           rx_enabled(port) = 1;//置端口为使能接收状态为1
    40           goto out;
    41         }
    42         continue;
    43       }
    44     }
    45
    46     /* 将接收到的字符写入buffer */
    47     flag = TTY_NORMAL;
    48     port->icount.rx++;
    49
    50     if (unlikely(uerstat &S3C2410_UERSTAT_ANY))
    51     {
    52       dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n", ch, uerstat);
    53
    54        if (uerstat &S3C2410_UERSTAT_BREAK)
    55       {
    56         dbg("break!\n");
    57         port->icount.brk++;
    58         if (uart_handle_break(port))
    59           goto ignore_char;
    60       }
    61
    62       if (uerstat &S3C2410_UERSTAT_FRAME)
    63         port->icount.frame++;
    64       if (uerstat &S3C2410_UERSTAT_OVERRUN)
    65         port->icount.overrun++;
    66
    67       uerstat &= port->read_status_mask;
    68
    69       if (uerstat &S3C2410_UERSTAT_BREAK)
    70         flag = TTY_BREAK;
    71       else if (uerstat &S3C2410_UERSTAT_PARITY)
    72         flag = TTY_PARITY;
    73       else if (uerstat &(S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_OVERRUN))
    74         flag = TTY_FRAME;
    75     }
    76
    77     if (uart_handle_sysrq_char(port, ch, regs)) //处理sysrq字符
    78       goto ignore_char;
    79     //插入字符到tty设备的flip 缓冲
    80     uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
    81
    82     ignore_char: continue;
    83   }
    84   tty_flip_buffer_push(tty);  //刷新tty设备的flip 缓冲
    85
    86   out: return IRQ_HANDLED;
    87 }
        上述代码第80行的uart_insert_char()函数是串口核心层对tty_insert_flip_char()的封装,它作为内联函数被定义于serial_core.h文件中。
    如代码清单14.34,s3c24xx_serial_tx_chars()读取uart_info中环形缓冲区中的字符,写入调用UTXHn寄存器。
    代码清单14.34 S3C2410串口驱动发送中断处理函数
    1  static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id, struct pt_regs
    2    *regs)
    3  {
    4    struct s3c24xx_uart_port *ourport = id;
    5    struct uart_port *port = &ourport->port;
    6    struct circ_buf *xmit = &port->info->xmit;  //得到环形缓冲区
    7    int count = 256;   //最多1次发256个字符

    9    if (port->x_char) //如果定义了xchar,发送
    10   {
    11     wr_regb(port, S3C2410_UTXH, port->x_char);
    12     port->icount.tx++;
    13     port->x_char = 0;
    14     goto out;
    15   }
    16
    17   /* 如果没有更多的字符需要发送,或者uart Tx停止,则停止uart并退出 */
    18   if (uart_circ_empty(xmit) || uart_tx_stopped(port))
    19   {
    20     s3c24xx_serial_stop_tx(port);
    21     goto out;
    22   }
    23
    24   /* 尝试把环行buffer中的数据发空 */
    25   while (!uart_circ_empty(xmit) && count-- > 0)
    26   {
    27     if (rd_regl(port, S3C2410_UFSTAT) &ourport->info->tx_fifofull)
    28       break;
    29
    30     wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
    31     xmit->tail = (xmit->tail + 1) &(UART_XMIT_SIZE - 1);
    32     port->icount.tx++;
    33   }
    34   /* 如果环形缓冲区中剩余的字符少于WAKEUP_CHARS,唤醒上层 */
    35   if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
    36     uart_write_wakeup(port);
    37
    38   if (uart_circ_empty(xmit)) //如果发送环形buffer为空
    39     s3c24xx_serial_stop_tx(port); //停止发送
    40
    41   out: return IRQ_HANDLED;
    42 }
        上述代码第35行的宏WAKEUP_CHARS的含义为:当发送环形缓冲区中的字符数小于该数时,驱动将请求上层向下传递更多的数据,uart_write_wakeup()完成此目的。
    uart_circ_chars_pending()、uart_circ_empty()是定义于serial_core.h中的宏,分别返回环形缓冲区剩余的字符数以及判断缓冲区是否为空。
    14.7.5 S3C2410串口线路设置
         S3C2410 串口驱动uart_ops结构体的set_termios()成员函数用于改变端口的参数设置,包括波特率、字长、停止位、奇偶校验等,它会根据传递给它 的port、termios参数成员的值设置S3C2410 UART的ULCONn、UCONn、UMCONn等寄存器,其实现如代码清单14.35。
    代码清单14.35 S3C2410串口驱动set_termios()函数
    1   static void s3c24xx_serial_set_termios(struct uart_port *port,
    2              struct termios *termios,
    3              struct termios *old)
    4   {
    5    struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
    6    struct s3c24xx_uart_port *ourport = to_ourport(port);
    7    struct s3c24xx_uart_clksrc *clksrc = NULL;
    8    struct clk *clk = NULL;
    9    unsigned long flags;
    10   unsigned int baud, quot;
    11   unsigned int ulcon;
    12   unsigned int umcon;
    13
    14   /* 不支持modem控制信号线 */
    15   termios->c_cflag &= ~(HUPCL | CMSPAR);
    16   termios->c_cflag |= CLOCAL;
    17
    18   /* 请求内核计算分频以便产生对应的波特率 */
    19   baud = uart_get_baud_rate(port, termios, old, 0, 115200*8);
    20
    21   if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
    22    quot = port->custom_divisor;
    23   else
    24    quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud);
    25
    26   /* 检查以确定是否需要改变时钟源 */
    27   if (ourport->clksrc != clksrc || ourport->baudclk != clk) {
    28    s3c24xx_serial_setsource(port, clksrc);
    29
    30    if (ourport->baudclk != NULL && !IS_ERR(ourport->baudclk)) {
    31     clk_disable(ourport->baudclk);
    32     ourport->baudclk  = NULL;
    33    }
    34
    35    clk_enable(clk);
    36
    37    ourport->clksrc = clksrc;
    38    ourport->baudclk = clk;
    39   }
    40
    41    /* 设置字长 */
    42   switch (termios->c_cflag & CSIZE) {
    43   case CS5:
    44    dbg("config: 5bits/char\n");
    45    ulcon = S3C2410_LCON_CS5;
    46    break;
    47   case CS6:
    48    dbg("config: 6bits/char\n");
    49    ulcon = S3C2410_LCON_CS6;
    50    break;
    51   case CS7:
    52    dbg("config: 7bits/char\n");
    53    ulcon = S3C2410_LCON_CS7;
    54    break;
    55   case CS8:
    56   default:
    57    dbg("config: 8bits/char\n");
    58    ulcon = S3C2410_LCON_CS8;
    59    break;
    60   }
    61
    62   /* 保留以前的lcon IR设置 */
    63   ulcon |= (cfg->ulcon & S3C2410_LCON_IRM);
    64
    65   if (termios->c_cflag & CSTOPB)
    66    ulcon |= S3C2410_LCON_STOPB;
    67    /* 设置是否采用RTS、CTS自动流空 */
    68   umcon = (termios->c_cflag & CRTSCTS) ? S3C2410_UMCOM_AFC : 0;
    69
    70   if (termios->c_cflag & PARENB) {
    71    if (termios->c_cflag & PARODD)
    72     ulcon |= S3C2410_LCON_PODD;//计校验
    73    else
    74     ulcon |= S3C2410_LCON_PEVEN;//偶校验
    75   } else {
    76    ulcon |= S3C2410_LCON_PNONE;//无校验
    77   }
    78
    79   spin_lock_irqsave(&port->lock, flags);
    80
    81   dbg("setting ulcon to %08x, brddiv to %d\n", ulcon, quot);
    82
    83   wr_regl(port, S3C2410_ULCON, ulcon);
    84   wr_regl(port, S3C2410_UBRDIV, quot);
    85   wr_regl(port, S3C2410_UMCON, umcon);
    86
    87   dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n",
    88       rd_regl(port, S3C2410_ULCON),
    89       rd_regl(port, S3C2410_UCON),
    90       rd_regl(port, S3C2410_UFCON));
    91
    92   /* 更新端口的超时 */
    93   uart_update_timeout(port, termios->c_cflag, baud);
    94
    95   /* 我们对什么字符状态状态标志感兴趣?*/
    96   port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
    97   if (termios->c_iflag & INPCK)
    98    port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY;
    99
    100  /* 我们要忽略什么字符状态标志?*/
    101  port->ignore_status_mask = 0;
    102  if (termios->c_iflag & IGNPAR)
    103   port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
    104  if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
    105   port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;
    106
    107  /* 如果CREAD未设置,忽略所用字符 */
    108  if ((termios->c_cflag & CREAD) == 0)
    109   port->ignore_status_mask |= RXSTAT_DUMMY_READ;
    110
    111  spin_unlock_irqrestore(&port->lock, flags);
    112 }
        由于S3C2410集成UART并不包含完整的Modem控制信号线,因此其uart_ops结构体的get_mctrl()、set_mctrl()成员 函数的实现非常简单,如代码清单14.36,get_mctrl()返回DSR一直有效,而CTS则根据UMSTATn寄存器的内容获得, set_mctrl()目前为空。
    代码清单14.36 S3C2410串口驱动get_mctrl()和set_mctrl()函数
    1  static unsigned int s3c24xx_serial_get_mctrl(struct uart_port *port)
    2  {
    3   unsigned int umstat = rd_regb(port,S3C2410_UMSTAT);

    5   if (umstat & S3C2410_UMSTAT_CTS)//CTS信号有效(低电平)
    6    return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
    7   else
    8    return TIOCM_CAR | TIOCM_DSR;
    9  }
    10
    11 static void s3c24xx_serial_set_mctrl(struct uart_port *port, unsigned int mctrl)
    12 {
    13  /* todo:可能移除AFC,并手工进行CTS */
    14 }
    14.8总结
        TTY设备驱动的主体工作围绕tty_driver这个结构体的成员函数展开,主要应实现其中的数据发送和接收流程以及tty设备线路设置接口函数。
    针对串口,内核实现了串口核心层,这个层实现了串口设备通用的tty_driver。因此,串口设备驱动的主体工作从tty_driver转移到了uart_driver。



    展开全文
  • Linux终端tty设备驱动 在Linux系统中,终端设备非常重要,没有终端设备,系统将无法向用户反馈信息,Linux中包含控制台、串口和伪终端3类终端设备。 14.1节阐述了终端设备的概念及分类,14.2节给出了Linux...

     


    Linux终端tty设备驱动



    在Linux系统中,终端设备非常重要,没有终端设备,系统将无法向用户反馈信息,Linux中包含控制台、串口和伪终端3类终端设备。
    14.1节阐述了终端设备的概念及分类,14.2节给出了Linux终端设备驱动的框架结构,重点描述tty_driver结构体及其成员。14.3~14.5节在14.2节的基础上,分别给出了Linux终端设备驱动模块加载/卸载函数和open()、close()函数,数据读写流程及tty设备线路设置的编程方法。在Linux中,串口驱动完全遵循tty驱动的框架结构,但是进行了底层操作的再次封装,14.6节描述了Linux针对串口tty驱动的这一封装,14.7节则具体给出了串口tty驱动的实现方法。14.8节基于14.6和14.7节的讲解给出了串口tty驱动的设计实例,即S3C2410集成UART的驱动。

    14.1终端设备
       在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司生产的。Linux中包含如下几类终端设备:
    1、串行端口终端(/dev/ttySn)
        串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。这些串行端口所对应的设备名称是 /dev/ttyS0(或/dev/tts/0)、/dev/ttyS1(或/dev/tts/1)等,设备号分别是(4,0)、(4,1)等。
        在命令行上把标准输出重定向到端口对应的设备文件名上就可以通过该端口发送数据,例如,在命令行提示符下键入: echo test &gt; /dev/ttyS1会把单词“test”发送到连接在ttyS1端口的设备上。
    2.伪终端(/dev/pty/)
        伪终端(Pseudo Terminal)是成对的逻辑终端设备,并存在成对的设备文件,如/dev/ptyp3和/dev/ttyp3,它们与实际物理设备并不直接相关。如果一个程序把ttyp3看作是一个串行端口设备,则它对该端口的读/写操作会反映在该逻辑终端设备对应的ptyp3上,而ptyp3则是另一个程序用于读写操作的逻辑设备。这样,两个程序就可以通过这种逻辑设备进行互相交流,使用ttyp3的程序会认为自己正在与一个串行端口进行通信。
        以telnet 为例,如果某人在使用telnet程序连接到Linux系统,则telnet程序就可能会开始连接到设备ptyp2上,而此时一个getty程序会运行在对应的ttyp2端口上。当telnet从远端获取了一个字符时,该字符就会通过ptyp2、ttyp2传递给 getty程序,而getty程序则会通过ttyp2、ptyp2和telnet程序返回“login:”字符串信息。这样,登录程序与telnet程序 就通过伪终端进行通信。通过使用适当的软件,可以把2个或多个伪终端设备连接到同一个物理串行端口上。
    3.控制台终端(/dev/ttyn, /dev/console)
        如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。可以使用命令“ps –ax”来查看进程与哪个控制终端相连使用命令“tty”可以查看它具体对应哪个实际终端设备。/dev/tty有些类似于到实际所使用终端设备的一个联接。
        在UNIX系统中,计算机显示器通常被称为控制台终端(Console)。它仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等。当用户在控制台上登录时,使用的是tty1。使用Alt+[F1—F6]组合键时,我们就可以切换到tty2、tty3等上面去。tty1–tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,系统所产生的信息会发送到该终端上。因此不管当前正在使用哪个虚拟终端,系统信息都会发送到控制台终端上。用户可以登录到不同的虚拟终端上去,因而可以让系统同时有几个不同的会话期存在。只有系统或超级用户root可以向/dev/tty0进行写操作。
    在Linux 中,可以在系统启动命令行里指定当前的输出终端,格式如下:
    console=device, options
    device指代的是终端设备,可以是tty0(前台的虚拟终端)、ttyX(第X个虚拟终端)、ttySX(第X个串口)、lp0(第一个并口)等。options指代对device进行的设置,它取决于具体的设备驱动。对于串口设备,参数用来定义为:波特率、校验位、位数,格式为BBBBPN,其中BBBB表示波特率,P表示校验(n/o/e),N表示位数,默认options是9600n8。
        用户可以在内核命令行中同时设定多个终端,这样输出将会在所有的终端上显示,而当用户调用open()打开/dev/console时,最后一个终端将会返回作为当前值。例如:
    console=ttyS1, 9600 console=tty0
    定义了2个终端,而调用open()打开/dev/console时,将使用虚拟终端tty0。但是内核消息会在tty0 VGA虚拟终端和串口ttyS1上同时显示。
        通过查看/proc/tty/drivers文件可以获知什么类型的tty设备存在以及什么驱动被加载到内核,这个文件包括一个当前存在的不同 tty 驱动的列表,包括驱动名、缺省的节点名、驱动的主编号、这个驱动使用的次编号范围,以及 tty 驱动的类型。例如,下面给出了一个/proc/tty/drivers文件的例子:

    14.2终端设备驱动结构
        Linux内核中 tty的层次结构如图14.1所示,包含tty核心、tty线路规程和tty驱动,tty 线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式,例如 PPP 和 Bluetooth。tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个 tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。接收数据的流程为: 从tty硬件接收到的数据向上交给tty驱动,进入tty线路规程驱动,再进入 tty 核心,在这里它被一个用户获取。尽管大多数时候tty核心和tty之间的数据传输会经历tty线路规程的转换,但是tty驱动与tty核心之间也可以直接传输数据。
                     &lt;!--[if !vml]--&gt;&lt;!--[endif]--&gt;
                               图14.1 tty分层结构
        图14.2显示了与tty相关的主要源文件及数据的流向。tty_io.c定义了tty 设备通用的file_operations结构体并实现了接口函数tty_register_driver()用于注册tty设备,它会利用 fs/char_dev.c提供的接口函数注册字符设备,与具体设备对应的tty驱动将实现tty_driver结构体中的成员函数。同时 tty_io.c也提供了tty_register_ldisc()接口函数用于注册线路规程,n_tty.c文件则实现了tty_disc结构体中的成员。
                     &lt;!--[if !vml]--&gt;&lt;!--[endif]--&gt;
    图14.2 tty主要源文件关系及数据流向
    从图14.2可以看出,特定tty设备驱动的主体工作是填充tty_driver结构体中的成员,实现其中的成员函数,tty_driver结构体的定义如代码清单14.1。
    代码清单14.1 tty_driver结构体
    1  struct tty_driver                                                            
    2  {                                                                             
    3    int magic;                                                                  
    4    struct cdev cdev; /* 对应的字符设备cdev */                                                           
    5    struct module *owner;   /*这个驱动的模块拥有者 */                                                   
    6    const char *driver_name;                                                   
    7    const char *devfs_name;                                                     
    8    const char *name;   /* 设备名 */                                                        
    9    int name_base; /* offset of printed name */                     
    10   int major; /* 主设备号 */                                                   
    11   int minor_start; /* 开始次设备号 */                     
    12   int minor_num; /* 设备数量 */                                               
    13   int num; /* 被分配的设备数量 */                                             
    14   short type; /* tty驱动的类型 */                        
    15   short subtype; /* tty驱动的子类型 */              
    16   struct termios init_termios; /* 初始线路设置 */              
    17   int flags; /* tty驱动标志 */                           
    18   int refcount; /*引用计数(针对可加载的tty驱动) */                    
    19   struct proc_dir_entry *proc_entry; /* /proc文件系统入口 */         
    20   struct tty_driver *other; /* 仅对PTY驱动有意义 */   &nb
    在Linux系统中,终端设备非常重要,没有终端设备,系统将无法向用户反馈信息,Linux中包含控制台、串口和伪终端3类终端设备。
    14.1节阐述了终端设备的概念及分类,14.2节给出了Linux终端设备驱动的框架结构,重点描述tty_driver结构体及其成员。14.3~14.5节在14.2节的基础上,分别给出了Linux终端设备驱动模块加载/卸载函数和open()、close()函数,数据读写流程及tty设备线路设置的编程方法。在Linux中,串口驱动完全遵循tty驱动的框架结构,但是进行了底层操作的再次封装,14.6节描述了Linux针对串口tty驱动的这一封装,14.7节则具体给出了串口tty驱动的实现方法。14.8节基于14.6和14.7节的讲解给出了串口tty驱动的设计实例,即S3C2410集成UART的驱动。

    14.1终端设备
       在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司生产的。Linux中包含如下几类终端设备:
    1、串行端口终端(/dev/ttySn)
        串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。这些串行端口所对应的设备名称是 /dev/ttyS0(或/dev/tts/0)、/dev/ttyS1(或/dev/tts/1)等,设备号分别是(4,0)、(4,1)等。
        在命令行上把标准输出重定向到端口对应的设备文件名上就可以通过该端口发送数据,例如,在命令行提示符下键入: echo test &gt; /dev/ttyS1会把单词“test”发送到连接在ttyS1端口的设备上。
    2.伪终端(/dev/pty/)
        伪终端(Pseudo Terminal)是成对的逻辑终端设备,并存在成对的设备文件,如/dev/ptyp3和/dev/ttyp3,它们与实际物理设备并不直接相关。如果一个程序把ttyp3看作是一个串行端口设备,则它对该端口的读/写操作会反映在该逻辑终端设备对应的ptyp3上,而ptyp3则是另一个程序用于读写操作的逻辑设备。这样,两个程序就可以通过这种逻辑设备进行互相交流,使用ttyp3的程序会认为自己正在与一个串行端口进行通信。
        以telnet 为例,如果某人在使用telnet程序连接到Linux系统,则telnet程序就可能会开始连接到设备ptyp2上,而此时一个getty程序会运行在对应的ttyp2端口上。当telnet从远端获取了一个字符时,该字符就会通过ptyp2、ttyp2传递给 getty程序,而getty程序则会通过ttyp2、ptyp2和telnet程序返回“login:”字符串信息。这样,登录程序与telnet程序 就通过伪终端进行通信。通过使用适当的软件,可以把2个或多个伪终端设备连接到同一个物理串行端口上。
    3.控制台终端(/dev/ttyn, /dev/console)
        如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。可以使用命令“ps –ax”来查看进程与哪个控制终端相连使用命令“tty”可以查看它具体对应哪个实际终端设备。/dev/tty有些类似于到实际所使用终端设备的一个联接。
        在UNIX系统中,计算机显示器通常被称为控制台终端(Console)。它仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等。当用户在控制台上登录时,使用的是tty1。使用Alt+[F1—F6]组合键时,我们就可以切换到tty2、tty3等上面去。tty1–tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,系统所产生的信息会发送到该终端上。因此不管当前正在使用哪个虚拟终端,系统信息都会发送到控制台终端上。用户可以登录到不同的虚拟终端上去,因而可以让系统同时有几个不同的会话期存在。只有系统或超级用户root可以向/dev/tty0进行写操作。
    在Linux 中,可以在系统启动命令行里指定当前的输出终端,格式如下:
    console=device, options
    device指代的是终端设备,可以是tty0(前台的虚拟终端)、ttyX(第X个虚拟终端)、ttySX(第X个串口)、lp0(第一个并口)等。options指代对device进行的设置,它取决于具体的设备驱动。对于串口设备,参数用来定义为:波特率、校验位、位数,格式为BBBBPN,其中BBBB表示波特率,P表示校验(n/o/e),N表示位数,默认options是9600n8。
        用户可以在内核命令行中同时设定多个终端,这样输出将会在所有的终端上显示,而当用户调用open()打开/dev/console时,最后一个终端将会返回作为当前值。例如:
    console=ttyS1, 9600 console=tty0
    定义了2个终端,而调用open()打开/dev/console时,将使用虚拟终端tty0。但是内核消息会在tty0 VGA虚拟终端和串口ttyS1上同时显示。
        通过查看/proc/tty/drivers文件可以获知什么类型的tty设备存在以及什么驱动被加载到内核,这个文件包括一个当前存在的不同 tty 驱动的列表,包括驱动名、缺省的节点名、驱动的主编号、这个驱动使用的次编号范围,以及 tty 驱动的类型。例如,下面给出了一个/proc/tty/drivers文件的例子:

    14.2终端设备驱动结构
        Linux内核中 tty的层次结构如图14.1所示,包含tty核心、tty线路规程和tty驱动,tty 线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式,例如 PPP 和 Bluetooth。tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个 tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。接收数据的流程为: 从tty硬件接收到的数据向上交给tty驱动,进入tty线路规程驱动,再进入 tty 核心,在这里它被一个用户获取。尽管大多数时候tty核心和tty之间的数据传输会经历tty线路规程的转换,但是tty驱动与tty核心之间也可以直接传输数据。
                     &lt;!--[if !vml]--&gt;&lt;!--[endif]--&gt;
                               图14.1 tty分层结构
        图14.2显示了与tty相关的主要源文件及数据的流向。tty_io.c定义了tty 设备通用的file_operations结构体并实现了接口函数tty_register_driver()用于注册tty设备,它会利用 fs/char_dev.c提供的接口函数注册字符设备,与具体设备对应的tty驱动将实现tty_driver结构体中的成员函数。同时 tty_io.c也提供了tty_register_ldisc()接口函数用于注册线路规程,n_tty.c文件则实现了tty_disc结构体中的成员。
                     &lt;!--[if !vml]--&gt;&lt;!--[endif]--&gt;
    图14.2 tty主要源文件关系及数据流向
    从图14.2可以看出,特定tty设备驱动的主体工作是填充tty_driver结构体中的成员,实现其中的成员函数,tty_driver结构体的定义如代码清单14.1。
    代码清单14.1 tty_driver结构体
    1  struct tty_driver                                                            
    2  {                                                                             
    3    int magic;                                                                  
    4    struct cdev cdev; /* 对应的字符设备cdev */                                                           
    5    struct module *owner;   /*这个驱动的模块拥有者 */                                                   
    6    const char *driver_name;                                                   
    7    const char *devfs_name;                                                     
    8    const char *name;   /* 设备名 */                                                        
    9    int name_base; /* offset of printed name */                     
    10   int major; /* 主设备号 */                                                   
    11   int minor_start; /* 开始次设备号 */                     
    12   int minor_num; /* 设备数量 */                                               
    13   int num; /* 被分配的设备数量 */                                             
    14   short type; /* tty驱动的类型 */                        
    15   short subtype; /* tty驱动的子类型 */              
    16   struct termios init_termios; /* 初始线路设置 */              
    17   int flags; /* tty驱动标志 */                           
    18   int refcount; /*引用计数(针对可加载的tty驱动) */                    
    19   struct proc_dir_entry *proc_entry; /* /proc文件系统入口 */         
    20   struct tty_driver *other; /* 仅对PTY驱动有意义 */   &nb
    展开全文
  • 虽然一直做嵌入式Linux,宿主机和开发板通信天天都在用tty设备通信,但是其实自己对TTY设备及终端的概念认识几乎是0。对于Linux内核的终端、tty、控制台等概念的认识很模糊。由于在学习的时候碰到了重定向console的...
  • Linux终端tty设备驱动编程

    千次阅读 2014-11-14 16:17:54
    14.1节阐述了终端设备的概念及分类,14.2节给出了Linux终端设备驱动的框架结构,重点描述tty_driver结构体及其成员。14.3~14.5节在14.2节的基础上,分别给出了Linux终端设备驱动模块加载/卸载函数和open()、close()...
  • linux终端tty设备驱动模型分析

    千次阅读 2012-08-17 11:19:05
    Tty这个名称源于电传打字节的简称。在linux表示各种终端。终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标。输出设备显示器的控制 终端和串口终端....如上图所示,用户空间主要是通过设备文件同tty_core交互.tty_
  • 对于LINUX内核tty设备的一点理解

    千次阅读 2013-03-26 14:02:50
    虽然一直做嵌入式Linux,宿主机和开发板通信天天都在用tty设备通信,但是其实自己对TTY设备及终端的概念认识几乎是0。对于Linux内核的终端、tty、控制台等概念的认识很模糊。由于在学习的时候碰到了重定向console的...
  • linux2.6.28-tty设备驱动学习(二) 网址:http://blog.chinaunix.net/uid-21273878-id-1828727.html 本次目标是要实现在用户态下对tty驱动程序的数据读写。 首先来看一下tty设备的数据流通图...
  • linux-tty

    千次阅读 2013-10-28 22:11:00
    一、TTY设备 在*nix中,tty设备用来抽象串口类型的设备,它位于字符驱动之下,抽象了串口设备需要的特性、功能,抽象后的一个tty设备即可表示一个串行输入、输出接口(比如控制台口,串口、pty设备接口)。 TTY的...
  • Linux tty 终端设备驱动

    千次阅读 2013-12-17 17:41:47
    Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司生产的。Linux中包含如下几...
  • Linux TTY设备驱动程序原理

    千次阅读 2008-08-05 15:54:00
    如何写TTY设备驱动程序可以参考Linux Device Driver的第18章和例子程序tiny_tty.c,这里主要分析驱动程序调用的kernel API后面的细节.驱动程序主要做3件事:struct tty_driver *alloc_tty_driver(int lines)int tty_...
  • linux 终端 tty 简介

    2020-02-23 20:40:07
    TTY 是 Teletype 或 Teletypewriter 的缩写,原来是指电传打字机,后来这种设备逐渐键盘...为了支持这些 TTY 设备Linux 实现了一个叫做 TTY 的子系统。所以 TTY 既指终端,也指 LinuxTTY 子系统,当然 TTY 还...
  • LinuxTTY介绍

    2012-02-17 18:11:02
    Linux中,TTY也许是跟终端有关系的最为混乱的术语。TTY是TeleTYpe的一个老缩写。 tty也是一个Unix命令,用来给出当前终端设备的名称。 终端是一种字符型设备
  • 一键批量关闭 Linuxtty 的方法

    千次阅读 2019-11-04 16:26:08
    一键批量关闭 Linuxtty 的方法 一 背景 在日常工作中,由于各种原因,可能需要关闭一些不必要的 tty。比如:服务器被非法登录、忘记关闭某些设备上已登录的 tty、终止一些不使用的tty等场景就需要批量强制关闭 ...
  •   在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司生产的。   Linux中...
  • 【转】linux2.6.28-tty设备驱动学习

    千次阅读 2011-06-12 13:47:00
    linux2.6.28-tty设备驱动学习在Linux系统中,终端是一种字符设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司...
  • linux tty driver

    2016-12-12 22:05:23
    那时候,UNIX的发明人——汤普森,为了实现多用户能够同时登陆UNIX系统的目的,使用非常廉价的TTY设备连接电脑,通过TTY设备发送和接收来自UNIX系统的信息,实现系统连接多个TTY设备的功能,多个用户可以同时登陆...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 46,530
精华内容 18,612
关键字:

linux查看tty设备

linux 订阅