精华内容
下载资源
问答
  • 文章目录1.tty字符设备2.tty设备的产生历史3.tty设备文件4.ssh登录之后的tty 1.tty字符设备 在linux中,一切皆为文件,所有不同种类的类型都被抽象成文件(比如:块设备,socket套接字,pipe队列) 操作这些不同的...

    1.tty字符设备

    • 在linux中,一切皆为文件,所有不同种类的类型都被抽象成文件(比如:块设备,socket套接字,pipe队列)
    • 操作这些不同的类型就像操作文件一样,比如增删改查等
    • 块设备支持随机访问,而字符设备只能依据先后顺序来读取数据。最典型的字符设备就是tty

    2.tty设备的产生历史

    根据史料记载:

    An ASR33 Teletype - origin of the abbreviation tty.

    tty来源一种电传打印机(teletype),就像这样:

    在这里插入图片描述

    • 敲击键盘输入不同的字符,然后由打印机将字符打印在纸上
    • 历史不断在往前发展,出现了计算机之后,计算机模拟了teletype的模式:通过外部终端输入,将输入的字符打印在屏幕上
    • 在teletype与计算机之间用串口相连,并且在计算机上通过信号转换(模拟信号转换为数字信号),让计算机能够识别,从而操作计算机
    • 由于计算机厂商众多,每个厂商都有自己风格的输入设备,所以计算机为了兼容这些设备,开发了内核tty模块
                                
                               +-----------------+
                               |                 |
    +--------+                 | +-------------+ |
    |teletype|-----------------> |serial       | |
    +--------+                 | |communication| |
                               | +-----+-------+ |
                               |       |         |
                               |       v         |
                               |  +----------+   |        +----------+
                               |  |tty driver|   |------->| display  |
                               |  +----------+   |        +----------+
                               |                 |
                               |computer         |
                               +-----------------+
    
    

    3.tty设备文件

    • 登陆到操作系统(不使用SSH协议,而使用控制台直接登陆),首先查看当前进程号所使用的tty
    root@ubuntu:~# tty
    /dev/pts/4
    root@ubuntu:~# ll /dev/pts/4
    crw------- 1 jiwangreal tty 136, 4 Nov 24 13:46 /dev/pts/4
    当
    前所使用的是/dev/pts/4,并且pts/4也分配了主设备号与次设备号(关于主设备号与次设备号,
    请看之前的文章:块设备文件)
    
    • 查看进程打开的描述符
    root@ubuntu:~# echo $$
    2652
    root@ubuntu:~# ll /proc/2652/fd
    total 0
    dr-x------ 2 root root  0 Nov 24 13:47 ./
    dr-xr-xr-x 9 root root  0 Nov 24 13:47 ../
    lrwx------ 1 root root 64 Nov 24 13:47 0 -> /dev/pts/4
    lrwx------ 1 root root 64 Nov 24 13:47 1 -> /dev/pts/4
    lrwx------ 1 root root 64 Nov 24 13:47 2 -> /dev/pts/4
    lrwx------ 1 root root 64 Nov 24 13:47 255 -> /dev/pts/4
    
    
    进程打开了4个文件描述符,这四个文件描述符都是/dev/tty1,他们的作用分别是:
    0:标准输入
    1:标准输出
    2:标准错误
    255:这个比较特殊,主要用于当tty重置的时候对0,1,2的一份复制(个人观点是对tty之前的历史信息作为一份复制)
    
    

    4.ssh登录之后的tty

    刚才介绍的都是操作系统提供的控制台登陆之后的情况,如果用ssh服务登陆之后会产生什么情况呢?

    首先介绍一个非常重要的概念,伪终端pty:

    • pty是一对虚拟的字符设备,提供双向通信。pty一般由master与slave组成
    • pty的出现是为了满足现在的登陆需求:网络登陆(ssh登陆、telnet登陆等)、Xwindow等
    • 历史上有两套接口标准:分别是BSD与unix98,当前大多数pts都是基于unix98标准来实现的
    • unix98的工作流程:
      (1)进程对/dev/ptmx调用open(),返回pseudoterminal master(PTM)的文件描述符,并且在/dev/pts下创建pseudoterminal slave(PTS): /dev/pts/0
      (2)调用grantpt()修改PTS的文件权限;调用unlockpt()对PTS解锁;最后调用slavename()得到PTS文件名字
      (3)此时,PTM与PTS都已经正常打开,并且建立一条通道,两端分别连接PTM与PTS
      (4)进程对PTM写的数据可以从PTS读出来,反之亦然

    下面的未做验证…因为我用ssh登录的话情况不太一样,看原作者的,了解一下登录过程即可

    • 登录
      (1)当进程ssh client请求与sshd建立登陆连接的时候,经过TCP握手以及tls握手之后,确认是一个合法的请求,sshd会fork()一个子进程出来专门服务于这条连接
    [root@localhost ~]# ps -ef | grep sshd
    root       894     1  0 Nov25 ?        00:00:00 /usr/sbin/sshd -D
    root      3126   894  0 Nov25 ?        00:00:00 sshd: root@pts/0
    

    (2)子进程2768对/dev/ptmx调用open(),得到PTM的文件描述符以及PTS的文件名

    #这里使用strace跟踪sshd主进程和它创建的子进程,然后打开另外一个shell登陆服务器
    [root@localhost ~]# strace -p 894 -ff -o sshd
    strace: Process 894 attached
    strace: Process 3126 attached
    strace: Process 3127 attached
    strace: Process 3128 attached
    strace: Process 3129 attached
    strace: Process 3130 attached
    strace: Process 3131 attached
    strace: Process 3132 attached
    strace: Process 3133 attached
    strace: Process 3134 attached
    strace: Process 3135 attached
    strace: Process 3136 attached
    strace: Process 3137 attached
    strace: Process 3138 attached
    strace: Process 3139 attached
    strace: Process 3140 attached
    [root@localhost ~]# grep ptmx ./sshd.*
    ./sshd.3126:open("/dev/ptmx", O_RDWR)               = 8
    
    
    shd894创建了一个子进程3126用来处理这条TCP连接。进程对/dev/ptmx调用open(),
    得到PTM的文件描述符8
    

    (3)子进程3126在/dev/pts下创建了一个字符设备文件/dev/pts/0,8与/dev/pts/0成为一对master/slave

    (4)子进程3126会再fork()一个子进程3128,子进程3128打开/dev/pts/0 3个描述符(标准输入,标准输出,标准错误),并且执行操作系统默认的shell(本文中bash)

    [root@localhost ~]# ps -ef | grep 3126
    root      3126   894  0 03:16 ?        00:00:00 sshd: root@pts/0
    root      3128  3126  0 03:16 pts/3    00:00:00 -bash
    [root@localhost ~]# ls -l /proc/3128/fd
    total 0
    lrwx------ 1 root root 64 Nov 26 03:16 0 -> /dev/pts/0
    lrwx------ 1 root root 64 Nov 26 03:16 1 -> /dev/pts/0
    lrwx------ 1 root root 64 Nov 26 03:16 2 -> /dev/pts/0
    lrwx------ 1 root root 64 Nov 26 03:22 255 -> /dev/pts/0
    
    • 至此,通信流程大概是这样:
                             +----------------+
    +------------+           |                |
    | ssh client +---------->|      sshd      |
    +----+-------+           |                |
         |                   +--------+-------+
         |                            |
         |                            |
         |                         fork()
         |                            |
         |                            |
         |                            v
         |                       +----+-----+     fork()    +----------+      +-----+
         +---------------------->|pid: 3126 |-------------->|pid: 3128 |----->|bash |
                                 +-+--------+               +----------+      +-----+
                                   |                              ^
                                   |                              |
                           +-------+                              |
                    +------|--------------------------------+     |
                    |      |                 +-----------+  |     |
                    |      v                 |           |  |     |
                    |  +---------+    fd=8   +-----------+  |     |
                    |  |/dev/ptmx|---------->|/dev/pts/0 |--------+
                    |  +---------+           +-----------+  |
                    |                        |           |  |
                    |                        +-----------+  |
                    +---------------------------------------+
    
    步骤如下:
    (1)当ssh client发出一个ls命令,通过TCP连接来到31263126将ls写入PTM文件描述符82/dev/ptmx查询到关联记录 PTM:8对应PTS:/dev/pts/0,把ls转发到/dev/pts/0当中
    (331280 -> /dev/pts/0中读取之后执行ls
    (4)ls返回结果之后写入1 -> /dev/pts/0,然后根据关联记录回写到/dev/ptmx
    (53126/dev/ptmx读取之后返回到ssh client
    

    参考:

    https://www.cnblogs.com/MrVolleyball/p/10024540.html
    
    展开全文
  • TTY

    千次阅读 2016-11-21 13:22:29
    终端是一种字符设备,它有多种类型,通常使用tty来简称各种类型的终端设备tty是Teletype的缩写。Teletype是最早出现的一种终端设备,很象电传打字机(或者说就是),是由Teletype公司生产的。在Linux系统的设备...

          终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写。Teletype是最早出现的一种终端设备,很象电传打字机(或者说就是),是由Teletype公司生产的。在Linux系统的设备特殊文件目录/dev/下,终端特殊设备文件一般有以下几种: 

    1.串行端口终端(/dev/ttySn)

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

    2.伪终端(/dev/pty/)

           伪终端(Pseudo Terminal)是成对的逻辑终端设备,例如/dev/ptyp3和/dev/ttyp3(或着在设备文件系统中分别是/dev/pty/m3和/dev/pty/s3)。它们与实际物理设备并不直接相关。如果一个程序把ttyp3看作是一个串行端口设备,则它对该端口的读/写操作会反映在该逻辑终端设备对的另一个上面(ptyp3)。而ptyp3则是另一个程序用于读写操作的逻辑设备。这样,两个程序就可以通过这种逻辑设备进行互相交流,而其中一个使用ttyp3的程序则认为自己正在与一个串行端口进行通信。这很像是逻辑设备对之间的管道操作。 
           对于ttyp3(s3),任何设计成使用一个串行端口设备的程序都可以使用该逻辑设备。但对于使用ptyp3的程序,则需要专门设计来使用ptyp3(m3)逻辑设备。例如,如果某人在网上使用telnet程序连接到你的计算机上,则telnet程序就可能会开始连接到设备ptyp2(m2)上(一个伪终端端口上)。此时一个getty程序就应该运行在对应的ttyp2(s2)端口上。当telnet从远端获取了一个字符时,该字符就会通过m2、s2传递给getty程序,而getty程序就会通过s2、m2和telnet程序往网络上返回”login:”字符串信息。这样,登录程序与telnet程序就通过“伪终端”进行通信。通过使用适当的软件,就可以把两个甚至多个伪终端设备连接到同一个物理串行端口上。 
          在使用设备文件系统(device filesystem)之前,为了得到大量的伪终端设备特殊文件,使用了比较复杂的文件名命名方式。因为只存在16个ttyp(ttyp0—ttypf)的设备文件,为了得到更多的逻辑设备对,就使用了象q、r、s等字符来代替p。例如,ttys8和ptys8就是一个伪终端设备对。不过这种命名方式目前仍然在于RedHat等Linux系统中使用着。
          但Linux系统上的Unix98并不使用上述方法,而使用了”pty master”方式,例如/dev/ptm3。它的对应端则会被自动地创建成/dev/pts/3。这样就可以在需要时提供一个pty伪终端。目录/dev/pts是一个类型为devpts的文件系统,并且可以在被加载文件系统列表中看到。虽然“文件”/dev/pts/3看上去是设备文件系统中的一项,但其实它完全是一种不同的文件系统。

    3.控制终端(/dev/tty)

           如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程控制终端的设备特殊文件。可以使用命令”ps –ax”来查看进程与哪个控制终端相连。对于你登录的shell,/dev/tty就是你使用的终端,设备号是(5,0)。使用命令”tty”可以查看它具体对应哪个实际终端设备。/dev/tty有些类似于到实际所使用终端设备的一个联接或别名。

    4.控制台(/dev/ttyn, /dev/console) 

            在Linux系统中,计算机显示器通常被称为控制台终端或控制台(Console)。它仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等。当你在控制台上登录时,使用的是tty1。使用Alt+[F1—F6]组合键时,我们就可以切换到tty2、tty3等上面去。tty1 –tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,Linux系统所产生的信息都会发送到该终端上。因此不管当前我们正在使用哪个虚拟终端,系统信息都会发送到我们的屏幕上。
          你可以登录到不同的虚拟终端上去,因而可以让系统同时有几个不同的会话存在。但只有系统或超级用户root可以向/dev/tty0进行写操作,而且有时/dev/console会连接至/dev/tty0或/dev/tty1上。

    5.其它类型

           Linux系统中还针对很多不同的字符设备建有很多其它种类的终端设备特殊文件。例如针对ISDN设备的/dev/ttyIn终端设备等。

    2009-05-31                                                     

    注:源自原百度博客“至美心”

    展开全文
  • 一、知识准备 1、在linux中,一切皆为文件,所有不同种类的类型都被抽象成文件(比如:块设备,socket套接字,...最典型的字符设备就是tty 二、环境准备 组件 版本 OS CentOS Linux release 7.5.1804 ...

    一、知识准备

    1、在linux中,一切皆为文件,所有不同种类的类型都被抽象成文件(比如:块设备,socket套接字,pipe队列)
    2、操作这些不同的类型就像操作文件一样,比如增删改查等
    3、块设备支持随机访问,而字符设备只能依据先后顺序来读取数据。最典型的字符设备就是tty


    二、环境准备

    组件版本
    OSCentOS Linux release 7.5.1804


    三、什么是tty?

    根据史料记载:

    An ASR33 Teletype - origin of the abbreviation tty.

    tty来源一种电传打印机(teletype),就像这样:

    1416773-20181127094129107-1200079520.jpg

    ● 敲击键盘输入不同的字符,然后由打印机将字符打印在纸上
    ● 历史不断在往前发展,出现了计算机之后,计算机模拟了teletype的模式:通过外部终端输入,将输入的字符打印在屏幕上
    ● 在teletype与计算机之间用串口相连,并且在计算机上通过信号转换(模拟信号转换为数字信号),让计算机能够识别,从而操作计算机
    ● 由于计算机厂商众多,每个厂商都有自己风格的输入设备,所以计算机为了兼容这些设备,开发了内核tty模块

                                
                               +-----------------+
                               |                 |
    +--------+                 | +-------------+ |
    |teletype|-----------------> |serial       | |
    +--------+                 | |communication| |
                               | +-----+-------+ |
                               |       |         |
                               |       v         |
                               |  +----------+   |        +----------+
                               |  |tty driver|   |------->| display  |
                               |  +----------+   |        +----------+
                               |                 |
                               |computer         |
                               +-----------------+
    
    
    

    四、tty设备文件

    登陆到操作系统(不使用SSH协议,而使用控制台直接登陆),首先查看当前进程号所使用的tty

    [root@localhost ~]# tty
    /dev/tty1
    [root@localhost ~]# ls -l /dev/tty1
    crw--w---- 1 root tty 4, 1 Nov 20 23:24 /dev/tty1

    当前所使用的是/dev/tty1,并且tty1也分配了主设备号与次设备号(关于主设备号与次设备号,请看之前的文章:块设备文件)

    查看进程打开的描述符

    [root@localhost ~]# echo $$
    5598
    [root@localhost ~]# ls -l /proc/5598/fd
    total 0
    lrwx------ 1 root root 64 Nov 19 22:23 0 -> /dev/tty1
    lrwx------ 1 root root 64 Nov 19 22:23 1 -> /dev/tty1
    lrwx------ 1 root root 64 Nov 19 22:23 2 -> /dev/tty1
    lrwx------ 1 root root 64 Nov 19 22:23 255 -> /dev/tty1

    进程打开了4个文件描述符,这四个文件描述符都是/dev/tty1,他们的作用分别是:
    0:标准输入
    1:标准输出
    2:标准错误
    255:这个比较特殊,主要用于当tty重置的时候对0,1,2的一份复制(个人观点是对tty之前的历史信息作为一份复制)

    更多的信息,请拜读大神的书《Shell Scripting: Expert Recipes for Linux, Bash, and more》,这里是链接(大概在267页):
    https://doc.lagout.org/operating%20system%20/linux/Commands%20and%20Shell%20Programming/Shell%20Scripting.pdf


    五、ssh登陆之后的tty

    刚才介绍的都是操作系统提供的控制台登陆之后的情况,如果用ssh服务登陆之后会产生什么情况呢?

    首先介绍一个非常重要的概念,伪终端pty:
    ● pty是一对虚拟的字符设备,提供双向通信。pty一般由master与slave组成
    ● pty的出现是为了满足现在的登陆需求:网络登陆(ssh登陆、telnet登陆等)、Xwindow等
    ● 历史上有两套接口标准:分别是BSD与unix98,当前大多数pts都是基于unix98标准来实现的
    ● unix98的工作流程:
    (1)进程对/dev/ptmx调用open(),返回pseudoterminal master(PTM)的文件描述符,并且在/dev/pts下创建pseudoterminal slave(PTS): /dev/pts/0
    (2)调用grantpt()修改PTS的文件权限;调用unlockpt()对PTS解锁;最后调用slavename()得到PTS文件名字
    (3)此时,PTM与PTS都已经正常打开,并且建立一条通道,两端分别连接PTM与PTS
    (4)进程对PTM写的数据可以从PTS读出来,反之亦然


    下面重点介绍一下基于unix98实现的sshd pty(主要分为登陆阶段和执行命令阶段):

    登陆:

    (1)当进程ssh client请求与sshd建立登陆连接的时候,经过TCP握手以及tls握手之后,确认是一个合法的请求,sshd会fork()一个子进程出来专门服务于这条连接

    [root@localhost ~]# ps -ef | grep sshd
    root       894     1  0 Nov25 ?        00:00:00 /usr/sbin/sshd -D
    root      3126   894  0 Nov25 ?        00:00:00 sshd: root@pts/0

    (2)子进程3126/dev/ptmx调用open(),得到PTM的文件描述符以及PTS的文件名

    #这里使用strace跟踪sshd主进程和它创建的子进程,然后打开另外一个shell登陆服务器
    [root@localhost ~]# strace -p 894 -ff -o sshd
    strace: Process 894 attached
    strace: Process 3126 attached
    strace: Process 3127 attached
    strace: Process 3128 attached
    strace: Process 3129 attached
    strace: Process 3130 attached
    strace: Process 3131 attached
    strace: Process 3132 attached
    strace: Process 3133 attached
    strace: Process 3134 attached
    strace: Process 3135 attached
    strace: Process 3136 attached
    strace: Process 3137 attached
    strace: Process 3138 attached
    strace: Process 3139 attached
    strace: Process 3140 attached
    [root@localhost ~]# grep ptmx ./sshd.*
    ./sshd.3126:open("/dev/ptmx", O_RDWR)               = 8

    sshd894创建了一个子进程3126用来处理这条TCP连接。进程对/dev/ptmx调用open(),得到PTM的文件描述符8

    (2)子进程3126/dev/pts下创建了一个字符设备文件/dev/pts/08/dev/pts/0成为一对master/slave
    (3)子进程3126会再fork()一个子进程3128,子进程3128打开/dev/pts/0 3个描述符(标准输入,标准输出,标准错误),并且执行操作系统默认的shell(本文中bash)

    [root@localhost ~]# ps -ef | grep 3126
    root      3126   894  0 03:16 ?        00:00:00 sshd: root@pts/0
    root      3128  3126  0 03:16 pts/3    00:00:00 -bash
    [root@localhost ~]# ls -l /proc/3128/fd
    total 0
    lrwx------ 1 root root 64 Nov 26 03:16 0 -> /dev/pts/0
    lrwx------ 1 root root 64 Nov 26 03:16 1 -> /dev/pts/0
    lrwx------ 1 root root 64 Nov 26 03:16 2 -> /dev/pts/0
    lrwx------ 1 root root 64 Nov 26 03:22 255 -> /dev/pts/0

    至此,通信流程大概是这样:

                             +----------------+
    +------------+           |                |
    | ssh client +---------->|      sshd      |
    +----+-------+           |                |
         |                   +--------+-------+
         |                            |
         |                            |
         |                         fork()
         |                            |
         |                            |
         |                            v
         |                       +----+-----+     fork()    +----------+      +-----+
         +---------------------->|pid: 3126 |-------------->|pid: 3128 |----->|bash |
                                 +-+--------+               +----------+      +-----+
                                   |                              ^
                                   |                              |
                           +-------+                              |
                    +------|--------------------------------+     |
                    |      |                 +-----------+  |     |
                    |      v                 |           |  |     |
                    |  +---------+    fd=8   +-----------+  |     |
                    |  |/dev/ptmx|---------->|/dev/pts/0 |--------+
                    |  +---------+           +-----------+  |
                    |                        |           |  |
                    |                        +-----------+  |
                    +---------------------------------------+
    
    

    执行命令:

    (4)当ssh client发出一个ls命令,通过TCP连接来到31263126ls写入PTM文件描述符8
    (5)/dev/ptmx查询到关联记录 PTM:8对应PTS:/dev/pts/0,把ls转发到/dev/pts/0当中
    (6)31280 -> /dev/pts/0中读取之后执行ls
    (7)ls返回结果之后写入1 -> /dev/pts/0,然后根据关联记录回写到/dev/ptmx
    (8)3126/dev/ptmx读取之后返回到ssh client


    六、参考资料

    http://man7.org/linux/man-pages/man7/pty.7.html
    http://man7.org/linux/man-pages/man4/pts.4.html
    http://osr600doc.sco.com/en/SDK_sysprog/_Pseudo-tty_Drivers_em_ptm_and_p.html
    https://unix.stackexchange.com/questions/79334/how-does-a-linux-terminal-work



    至此,本文结束
    在下才疏学浅,有撒汤漏水的,请各位不吝赐教...

    转载于:https://www.cnblogs.com/MrVolleyball/p/10024540.html

    展开全文
  •   在Linux系统中,终端是一种字符设备,它有多种类型,通常使用tty来简称各种类型的终端设备tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司生产的。   Linux中...

    终端设备

      在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的程序会认为自己正在与一个串行端口进行通信。
    3. 控制台终端(/dev/ttyn, /dev/console)
      如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。在UNIX系统中,计算机显示器通常被称为控制台终端(Console)。它仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等。当用户在控制台上登录时,使用的是tty1。使用Alt+[F1—F6]组合键时,我们就可以切换到tty2、tty3等上面去。

    Linux终端设备驱动的框架结构

      Linux内核中 tty的层次结构包含tty核心、tty线路规程和tty驱动。
                       在这里插入图片描述
                           tty的层次结构图
      tty 线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式,例如 PPP 和 Bluetooth。

    • tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个 tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。
    • 接收数据的流程为: 从tty硬件接收到的数据向上交给tty驱动,进入tty线路规程驱动,再进入 tty 核心,在这里它被一个用户获取。尽管大多数时候tty核心和tty之间的数据传输会经历tty线路规程的转换,但是tty驱动与tty核心之间也可以直接传输数据。

    Linux终端设备的结构体

    tty_driver结构体

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

    struct tty_driver
    {
    	int magic;
    	struct cdev cdev;       /* 对应的字符设备cdev */
    	struct module owner;    /* 这个驱动的模块拥有者 */ 
    	const char * driver_name;
    	const char *	devfs_name;
    	const char		name;   /* 设备名 */ 
    	int name_base;					/* offset of printed name */
    	int 			major;							/* 主设备号 */
    	int 			minor_start;					/* 开始次设备号 */
    	int 			minor_num;						/* 设备数量 */
    	int 			num;							/* 被分配的设备数量 */
    	short			type;							/* tty驱动的类型 */
    	short			subtype;						/* tty驱动的子类型 */
    	struct termios init_termios;                     /* 初始线路设置 */
    	int 			flags;							/* tty驱动标志 */
    	int 			refcount;          // 引用计数(针对可加载的tty驱动)  
    	struct proc_dir_entry proc_entry;  // proc文件系统入口 */ 
    	struct tty_driver other;           // 仅对PTY驱动有意义 *//* 接口函数 */
    	int(*open) (struct tty_struct * tty, struct file * filp);
    	void(*close) (struct tty_struct * tty, struct file * filp);
    	int(*write) (struct tty_struct * tty, const unsigned char * buf, int count);
    	int(*ioctl) (struct tty_struct * tty, struct file * file, unsigned int cmd, 
    		unsigned long arg);
    	void(*stop) (struct tty_struct * tty);
    	void(*start) (struct tty_struct * tty);
    	void(*hangup) (struct tty_struct * tty);
    	……
    
    	struct list_head tty_drivers;
    };
    
    • magic表示给这个结构体的“幻数”,设为 TTY_DRIVER_MAGIC,在 alloc_tty_driver()函数中被初始化。
    • name与driver_name的不同在于后者表示驱动的名字,用在 /proc/tty 和 sysfs中,而前者表示驱动的设备节点名。
    • type 与subtype描述tty驱动的类型和子类型,subtype的值依赖于type,type成员的可能值为 TTY_DRIVER_TYPE_SYSTEM。
    • 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结构体表示为:
    struct termios tty_std_termios =
    {
    	.c_iflag = ICRNL | IXON,						/* 输入模式 */
    	.c_oflag = OPOST | ONLCR,						/* 输出模式 */
    	.c_cflag = B38400 | CS8 | CREAD | HUPCL,		/* 控制模式 */
    	.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN,
     /* 本地模式 */
    	.c_cc = INIT_C_CC				/* 控制字符,用来修改终端的特殊字符映射 */
    };
    
    • major、minor_start、minor_num表示主设备号、次设备号及可能的次设备数,name表示设备名(如ttyS)。
    • 后面的函数指针实际和tty_operations结构体等同,它们通常需在特定设备tty驱动模块初始化函数中被赋值。

    tty_operations结构体

      tty_operations中的成员函数与 tty_driver中的同名成员函数意义完全一致,其定义为:

    struct tty_operations
    {
    	int(open) (struct tty_struct tty, struct file * filp);     //打开函数
    	void(close) (struct tty_struct tty, struct file * filp);    //关闭函数
    	int(write) (struct tty_struct tty,                   //写函数
    		        const unsigned char * buf, int count);
    	void(*put_char) (struct tty_struct * tty, unsigned char ch);
    /*put_char()为单字节写函数,当单个字节被写入设备时这个函数被 tty 核心调用,如果一个 tty 驱动没有定义这个函数,将使用count参数为1的write()函数。*/
    	void(*flush_chars) (struct tty_struct * tty);
    	int(*write_room) (struct tty_struct * tty);
    /*flush_chars()与wait_until_sent()函数都用于刷新数据到硬件。write_room()指示有多少缓冲区空闲。*/
    	int(*chars_in_buffer) (struct tty_struct * tty);
    /*chars_in_buffer()指示缓冲区中包含的数据数。*/
    	int(ioctl) (struct tty_struct * tty, struct file file, 
    		        unsigned int cmd, unsigned long arg);
    /*控制函数,类似其它设备的ioctl函数*/
    	void(set_termios) (struct tty_struct * tty, struct termios old);
    /*当设备的 termios 设置被改变时,set_termios()函数将被tty核心调用。*/
    	void(throttle) (struct tty_struct tty);
    	void(unthrottle) (struct tty_struct tty);
    	void(*stop) (struct tty_struct * tty);
    	void(*start) (struct tty_struct * tty);
    /*throttle ()、unthrottle()、stop()和start()为数据抑制函数,这些函数用来帮助控制 tty 核心的输入缓存。当 tty 核心的输入缓冲满时,throttle()函数将被调用,tty驱动试图通知设备不应当发送字符给它。当 tty 核心的输入缓冲已被清空时,unthrottle()函数将被调用暗示设备可以接收数据。stop()和start()函数非常像throttle()和 unthrottle()函数,但它们表示 tty 驱动应当停止发送数据给设备以及恢复发送数据。 */
    	void(*hangup) (struct tty_struct * tty);
    /*当 tty驱动挂起 tty设备时,hangup()函数被调用,在此函数中进行相关的硬件操作*/
    	void(*flush_buffer) (struct tty_struct * tty);
    /*flush_buffer()函数用于刷新缓冲区并丢弃任何剩下的数据。*/
    	void(*set_ldisc) (struct tty_struct * tty);
    /*set_ldisc()函数用于设置线路规程,当 tty 核心改变tty驱动的线路规程时这个函数被调用,这个函数通常不需要被驱动定义。*/
    	int(*read_proc) (char * page, char * *start, off_t off, 
    		             int count, int * eof, void * data);
    	int(*write_proc) (struct file * file, const char __user *buffer, 
    		              unsigned long count, void * data);
    /*read_proc()和write_proc()为/proc 读和写函数。*/
    	int(*tiocmget) (struct tty_struct * tty, struct file * file);
    	int(*tiocmset) (struct tty_struct * tty, struct file * file, 
    		            unsigned int set, unsigned int clear);
    /*tiocmget()函数用于获得tty 设备的线路设置,对应的tiocmset()用于设置tty设备的线路设置,参数set和clear包含了要设置或者清除的线路设置。 */
    };
    

    tty_struct结构体

      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驱动和线路规程的“私有”数据。

    Linux终端设备的装载和卸载

      Linux内核提供了一组函数用于操作tty_driver结构体及tty设备,包括:

    tty设备的注册和注销

    注册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驱动的操作

    分配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_set_operations(struct tty_driver *driver, struct tty_operations *op);
    上述函数会将tty_operations结构体中的函数指针拷贝给tty_driver对应的函数指针,在具体的tty驱动中,通常会定义1个设备特定的 tty_operations。

    tty驱动的模块加载和卸载

      终端设备驱动都围绕tty_driver结构体而展开,一般而言,终端设备驱动应包含如下组成:

    • 终端设备驱动模块加载函数和卸载函数,完成注册和注销tty_driver,初始化和释放终端设备对应的tty_driver结构体成员及硬件资源。
    • 实现tty_operations结构体中的一系列成员函数,主要是实现open()、close()、write()、tiocmget()、tiocmset()等函数。

      tty驱动的模块加载函数中通常需要分配、初始化tty_driver结构体并申请必要的硬件资源,对应的模块加载函数模板为:

    static int __init xxx_init(void)
    {/* 分配tty_driver结构体 */
    	xxx_tty_driver	= alloc_tty_driver(XXX_PORTS);
    	/* 初始化tty_driver结构体 */
    	xxx_tty_driver->owner = THIS_MODULE;
    	xxx_tty_driver->devfs_name = “tts /;
    	xxx_tty_driver->name = “ttyS”;
    	xxx_tty_driver->major = TTY_MAJOR;
    	xxx_tty_driver->minor_start = 64;
    	xxx_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
    	xxx_tty_driver->subtype = SERIAL_TYPE_NORMAL;
    	xxx_tty_driver->init_termios = tty_std_termios;
    	xxx_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    	xxx_tty_driver->flags = TTY_DRIVER_REAL_RAW;
    	tty_set_operations(xxx_tty_driver, &xxx_ops);
    	ret 	= tty_register_driver(xxx_tty_driver);    //注册驱动
    	if (ret)
    		{
    		printk(KERN_ERR “Couldn’t register xxx serial driver \ n”);
    		put_tty_driver(xxx_tty_driver);
    		return ret;
    		}
    ret = request_irq();                   /* 硬件资源申请 */ 
    }
    

      卸载函数的话就是把已经注册的设备和驱动调用相关的注销函数。

    Linux终端设备的函数操作

    打开和关闭函数

      当用户对tty驱动所分配的设备节点进行open()系统调用时,tty_driver中的open()成员函数将被tty核心调用。tty 驱动必须设置open()成员,否则,-ENODEV将被返回给调用open()的用户。open()成员函数的第1个参数为一个指向分配给这个设备的 tty_struct 结构体的指针,第2个参数为文件指针:

    static int xxx_open(struct tty_struct * tty, struct file * file)
    {
    	struct xxx_tty * xxx;
    	/* 分配xxx_tty内存 */
    	xxx 	= kmalloc(sizeof(*xxx), GFP_KERNEL);
    	if (!xxx)
    		return - ENOMEM;
    	/* 初始化xxx_tty中的成员 */
    	init_MUTEX(&xxx->sem);
    	xxx->open_count 	= 0;/* 让tty_struct中的driver_data指向xxx_tty */
    	tty->driver_data	= xxx;
    	xxx->tty			= tty;return 0;
    }
    

      在用户对前面使用 open()系统调用而创建的文件句柄进行close()系统调用时,tty_driver中的close()成员函数将被tty核心调用。

    数据发送和接收函数

       用户在有数据发送给终端设备时,通过“write()系统调用――tty核心――线路规程”的层层调用,最终调用tty_driver结构体中的write()函数完成发送。
      因为速度和tty硬件缓冲区容量的原因,不是所有的写程序要求的字符都可以在调用写函数时被发送,因此写函数应当返回能够发送给硬件的字节数以便用户程序检查是否所有的数据被真正写入。如果在 wirte()调用期间发生任何错误,一个负的错误码应当被返回。
      tty_driver 的write()函数接受3个参数tty_struct、发送数据指针及要发送的字节数,一般首先会通过tty_struct的driver_data成员得到设备私有信息结构体,然后依次进行必要的硬件操作开始发送:

    static int xxx_write(struct tty_struct * tty, const unsigned char * buf, int count)
    {
    	/* 获得tty设备私有数据 */
    	struct xxx_tty xxx = (struct xxx_tty)
    	tty->driver_data;while (1)        /* 开始发送 */
    		{
    		local_irq_save(flags);
    		c= min_t(int, count, min(SERIAL_XMIT_SIZE - xxx->xmit_cnt - 1, 
    			SERIAL_XMIT_SIZE - xxx->xmit_head));
    		if (c <= 0)
    			{
    			local_irq_restore(flags);
    			break;
    			}
    		//拷贝到发送缓冲区 
    		memcpy(xxx->xmit_buf + xxx->xmit_head, buf, c);
    		xxx->xmit_head	= (xxx->xmit_head + c) & (SERIAL_XMIT_SIZE - 1);
    		xxx->xmit_cnt		+= c;
    		local_irq_restore(flags);
    		buf += c;
    		count -= c;
    		total	+= c;
    		}
    	if (xxx->xmit_cnt && !tty->stopped && !tty->hw_stopped)
    		{
    		start_xmit(xxx);							//开始发送 
    		}
    	return total;    //返回发送的字节数 
    }
    

      当tty子系统自己需要发送数据到 tty 设备时,如果没有实现 put_char()函数,write()函数将被调用,此时传入的count参数为1。
      tty_driver结构体中没有提供 read()函数。因为发送是用户主动的,而接收即用户调read()则是读一片缓冲区中已放好的数据。tty 核心在一个称为 struct tty_flip_buffer 的结构体中缓冲数据直到它被用户请求。因为tty核心提供了缓冲逻辑,因此每个 tty 驱动并非一定要实现它自身的缓冲逻辑。
      tty驱动不必过于关心tty_flip_buffer 结构体的细节,如果其count字段大于或等于TTY_FLIPBUF_SIZE,这个flip缓冲区就需要被刷新到用户,刷新通过对 tty_flip_buffer_push()函数的调用来完成:

    for (i = 0; i < data_size; ++i)
    {
    	if (tty->flip.count >= TTY_FLIPBUF_SIZE)
    		tty_flip_buffer_push(tty);                 //数据填满向上层“推” 
    	tty_insert_flip_char(tty, data[i], TTY_NORMAL);   //把数据插入缓冲区 
    }
    tty_flip_buffer_push(tty);
    

      从tty 驱动接收到字符通过tty_insert_flip_char()函数被插入到flip缓冲区。
      该函数的第1个参数是数据应当保存入的 tty_struct结构体,第 2 个参数是要保存的字符,第3个参数是应当为这个字符设置的标志,如果字符是一个接收到的常规字符,则设为TTY_NORMAL,如果是一个特殊类型的指示错误的字符,依据具体的错误类型,应当设为TTY_BREAK、 TTY_PARITY或TTY_OVERRUN。

    展开全文
  • Linux的tty设备介绍

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

    千次阅读 2017-04-30 14:34:25
    虽然一直做嵌入式Linux,宿主机和开发板通信天天都在用tty设备通信,但是其实自己对TTY设备及终端的概念认识几乎是0。对于Linux内核的终端、tty、控制台等概念的认识很模糊。由于在学习的时候碰到了重定向console的...
  • /dev/null 空设备(位桶:bit bucket) /dev/tty 用户登录终端的伪设备 /dev/console 系统控制台的通用名字 /dev/ttynn 直接连接的终端 /dev/ttyxnn 多路的终端(x典型的是a、b等) ...
  • 1. struct tty_operations struct tty_operations { struct tty_struct * (*lookup)(struct tty_driver *driver, struct inode *inode, int idx); int (*install)(struct tty_driver *driver, struct tty_str....
  • /dev/tty设备 这个设备表示的是控制终端,如果当前的shell登录环境有关联控制终端,那么执行它就可以看到回显。 echo test > /dev/tty 它其实是一个当前控制终端的一个别名,实际控制终端可以是伪终端(/dev/pts/...
  • 在 Linux 系统中,终端是一种字符设备,它有多种类型,通常使用 tty 来简称各种类型的 终端设备tty 是 Teletype 的缩写,Teletype 是最早出现的一种终端设备,很像电传打字 机,是由 Teletype 公司生产的。Linux...
  • TTY设备

    千次阅读 2018-09-16 16:18:43
    在*nix中,tty设备用来抽象串口类型的设备,它位于字符驱动之下,抽象了串口设备需要的特性、功能,抽象后的一个tty设备即可表示一个串行输入、输出接口(比如控制台口,串口、pty设备接口)。 TTY的实现由两部分...
  • 前面有一个资源是讲述终端设备基本概念的。 这个是终端设备tty的深入版本。从终端设备的架构出发,深入讲解终端设备。 相信这对需要了解终端设备tty的人员会有相当大的帮助。
  • 值得注意的是,内核为了简化某些常用字符设备驱动的设备,也会提供一个中间层作为缓冲,如 TTY 中间层。用户可以自行选择是否使用这些中间层。一般而言,对于UART设备,建议读者使用内核提供的 TTY 中间层,以提高...
  • linux字符设备

    2020-02-19 14:01:33
    它们均以一个文件节点形式显示在文件系统的/dev目录下(crw--w---- 1 root tty 4, 0 7月 11 09:11 tty0 其中c代表字符设备类型)。 字符设备是指设备无需缓冲即可直接进行读写的设备, 如鼠标,键盘,串口设备等, 它...
  • Linux终端tty设备驱动

    千次阅读 2016-09-14 09:51:49
    14.1节阐述了终端设备的概念及分类,14.2节给 出了Linux终端设备驱动的框架结构,重点描述tty_driver结构体及其成员。14.3~14.5节在14.2节的基础上,分别给出了Linux 终端设备驱动模块加载/卸载函数和open()、close...
  • 在上一章我们主要介绍了tty子系统的软件架构,并简要说明了数据结构间的关系,本...struct tty_file_private struct file struct tty_struct struct tty_driver struct tty_operations struct tty_ldisc struct tty...
  • Tty这个名称源于电传打字节的简称。在linux表示各种终端。终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标。输出设备显示器的控制 终端和串口终端.也有对应于不存在设备的pty驱动。在如此众多的终端模型之中,...
  • tty设备驱动注册简述

    2017-12-23 14:49:12
    tty设备驱动注册简述
  • 测试某个tty设备是否可用

    千次阅读 2017-05-03 10:35:18
    通过echo 命令直接往/dev/ttyN 设备写字符,可以显示的话,就说明这个tty设备可用。例如下图中就可以看到/dev/ttyAMA0这个设备可用,而/dev/tty0 这个设备不可用
  • Linux字符设备驱动剖析

    千次阅读 2015-05-23 23:09:13
    忠于源码,讲述linux字符设备驱动的那些事儿,重点讲述字符设备的创建和访问过程。
  • Linux中tty是什么(tty1~7)

    千次阅读 2019-10-03 15:33:59
    tty:终端设备的统称。 tty一词源于Teletypes,或者...终端是一种字符设备,它有多种类型,通常使用tty来简称各种类型的终端设备tty1~6是文本型控制台,tty7是X Window图形显示管理器。 在本地机器上...
  • 本章主要介绍tty字符设备文件对应的操作接口,从而说明tty设备的数据打开、关闭、读、写等接口的实现等内容。 tyy file_operations定义 tty字符设备文件操作接口的定义如下,主要包括tty_fops、console_fops...
  • 字符设备驱动实验

    千次阅读 2019-06-11 08:38:54
    一、字符设备基础 字符设备:是指只能一个字节一个字节进行读写操作的设备,不能随机读取设备中的某一数据、读取数据要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED等。 ...
  • Linux终端tty设备驱动编程

    千次阅读 2014-11-14 16:17:54
    14.1节阐述了终端设备的概念及分类,14.2节给出了Linux终端设备驱动的框架结构,重点描述tty_driver结构体及其成员。14.3~14.5节在14.2节的基础上,分别给出了Linux终端设备驱动模块加载/卸载函数和open()、close()...
  • 第一点就是用mknod创建的设备名,设备号不能随便写,必须你所写的源文件命名的一致。 比如你在c文件中定义#define DEV_NAME &quot;chardev&quot;那么设备名就是chardev 设备号可以通过 cat /proc/d
  • 在上一章中我们介绍了tty子系统相关的数据结构,本章我们主要介绍tty driver的注册与注销、线路... 主要实现tty controller 驱动的注册与注销操作,相应的函数包括tty_register_driver、tty_unregister_driver,下面...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,663
精华内容 12,665
关键字:

tty1设备是字符设备