终端是人们用来和Unix进程进行通信的设备。终端拥有一个可以让进程读取字符的键盘和可让进程发送字符的显示器。终端是一个设备,所以它在目录树中表现为一个特殊的文件,通常在/dev这个目录中。
进程和终端间的数据传输和数据处理由终端驱动程序负责,终端驱动程序时内核的一部分。该内核代码提供缓冲、编辑、和数据转换。程序可以通过tcgetattr和tcsetattr查看和修改驱动程序的设置。
tty也是一个Unix/Linux命令,用来给出当前终端设备的名称。stty命令让用户读取和修改终端驱动程序的位置。
终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。
每个加载到Unix机器的设备(终端 打印机 鼠标 磁盘等)都通过文件名表示。通常,表示设备的文件存放在目录/dev中,但是可以在任何目录中创建设备文件。

root@ubuntu:~# ls /dev
autofs           loop5               sr0     tty33  tty63      ttyS6
block            loop6               sr1     tty34  tty7       ttyS7
bsg              loop7               stderr  tty35  tty8       ttyS8
btrfs-control    loop-control        stdin   tty36  tty9       ttyS9
bus              mapper              stdout  tty37  ttyprintk  uhid
cdrom            mcelog              tty     tty38  ttyS0      uinput
cdrw             media0              tty0    tty39  ttyS1      urandom
char             mem                 tty1    tty4   ttyS10     usb
console          memory_bandwidth    tty10   tty40  ttyS11     userio
core             mqueue              tty11   tty41  ttyS12     v4l
cpu_dma_latency  net                 tty12   tty42  ttyS13     vcs
cuse             network_latency     tty13   tty43  ttyS14     vcs1
disk             network_throughput  tty14   tty44  ttyS15     vcs2
dvd              null                tty15   tty45  ttyS16     vcs3
dvdrw            port                tty16   tty46  ttyS17     vcs4
ecryptfs         ppp                 tty17   tty47  ttyS18     vcs5
fb0              psaux               tty18   tty48  ttyS19     vcs6
fd               ptmx                tty19   tty49  ttyS2      vcsa
full             pts                 tty2    tty5   ttyS20     vcsa1
fuse             random              tty20   tty50  ttyS21     vcsa2
hpet             rfkill              tty21   tty51  ttyS22     vcsa3
hugepages        rtc                 tty22   tty52  ttyS23     vcsa4
hwrng            rtc0                tty23   tty53  ttyS24     vcsa5
initctl          sda                 tty24   tty54  ttyS25     vcsa6
input            sda1                tty25   tty55  ttyS26     vfio
kmsg             sda2                tty26   tty56  ttyS27     vga_arbiter
lightnvm         sda5                tty27   tty57  ttyS28     vhci
log              sg0                 tty28   tty58  ttyS29     vhost-net
loop0            sg1                 tty29   tty59  ttyS3      video0
loop1            sg2                 tty3    tty6   ttyS30     zero
loop2            shm                 tty30   tty60  ttyS31
loop3            snapshot            tty31   tty61  ttyS4
loop4            snd                 tty32   tty62  ttyS5
root@ubuntu:~# 

输入tty命令可以看到如下内容:

root@ubuntu:~# tty
/dev/pts/2
root@ubuntu:~# ls -li /dev/pts/2
5 crw------- 1 root tty 136, 2 Jun  4 22:37 /dev/pts/2
root@ubuntu:~# 

通过上面可以看到i结点为5,权限位位rw------- 1个链接 文件所有者是root,组为tty。文件类型是“C”,表示这个文件实际上是以字符为单位进行传送的设备。
136,2看起来有点怪,是什么意思呢?
136和2这两个数被称为设备的主设备号和从设备号。主设备号确定处理该设备实际的子程序,而从设备号被作为参数传输到该子程序。
我们可以用如下程序验证tty的一个特性:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int ac,char* av[])
{
    int fd;
    char buf[BUFSIZ];
    
    if(ac != 2)
    {
        fprintf(stderr,"usage: write0 ttyname\n");
        exit(1);
    }
    
    fd = open(av[1],O_WRONLY);
    if(fd==-1)
    {
        perror(av[1]);
        exit(1);
    }
    
    while(fgets(buf,BUFSIZ,stdin)!=NULL)
    {
        if(write(fd,buf,strlen(buf))==-1)
            break;
    }
    close(fd);
}


仔细阅读这段代码,在这里找不到键盘连接到其他用户屏幕所需的特殊特征。这个简单的write程序将一个文件的内容一行行地复制到另一个文件。这个程序表明终端就像其它连接到Unix机器的设备一样,能够以磁盘文件的方式被处理。
设备不仅具有文件名,而且支持与所有文件相关的系统调用:open,read,write,lseek,close,stat.

终端驱动程序

例子echostate.c---显示回显位的状态

#include        <stdio.h>
#include        <termios.h>
#include        <stdlib.h>
#include        <unistd.h>

int main()
{
        struct termios info;
        int rv;

        rv = tcgetattr( 0, &info );     /* read values from driver      */

        if ( rv == -1 ){
                perror( "tcgetattr");
                exit(1);
        }
        if ( info.c_lflag & ECHO )
                printf(" echo is on , since its bit is 1\n");
        else
                printf(" echo if OFF, since its bit is 0\n");
}

执行结果:

root@ubuntu:~/ulp/CH05# ./echostate
 echo is on , since its bit is 1
root@ubuntu:~/ulp/CH05# 

例子:setecho.c-改变回显位的状态

#include        <stdio.h>
#include        <termios.h>
#include        <stdlib.h>
#include        <unistd.h>
#define  oops(s,x) { perror(s); exit(x); }

int main(int ac, char *av[])
{
        struct termios info;

        if ( ac == 1 ) 
        exit(0);

        if ( tcgetattr(0,&info) == -1 )          /* get attribs   */
        oops("tcgettattr", 1);

        if ( av[1][0] == 'y' )
                info.c_lflag |= ECHO ;          /* turn on bit    */
        else
                info.c_lflag &= ~ECHO ;         /* turn off bit   */

        if ( tcsetattr(0,TCSANOW,&info) == -1 ) /* set attribs    */
               oops("tcsetattr",2);
}

执行结果:

root@ubuntu:~/ulp/CH05# clear

root@ubuntu:~/ulp/CH05# ls
echostate    listchar     setecho    showtty.c  write1.c
echostate.c  listchars.c  setecho.c  write0.c
root@ubuntu:~/ulp/CH05# ./echostate 
 echo is on , since its bit is 1
root@ubuntu:~/ulp/CH05# ./setecho n
root@ubuntu:~/ulp/CH05#  echo if OFF, since its bit is 0
root@ubuntu:~/ulp/CH05# root@ubuntu:~/ulp/CH05# 
root@ubuntu:~/ulp/CH05# ./echostate
 echo is on , since its bit is 1
root@ubuntu:~/ulp/CH05# 

当关闭回显时,用户输入内容不在屏幕显示,可以通过./setecho y开启回显。