终端是人们用来和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开启回显。