Chinaunix首页 | 论坛 | 博客
  • 博客访问: 599942
  • 博文数量: 60
  • 博客积分: 3993
  • 博客等级: 中校
  • 技术积分: 1572
  • 用 户 组: 普通用户
  • 注册时间: 2005-08-08 17:08
文章分类

全部博文(60)

文章存档

2012年(7)

2011年(35)

2010年(8)

2009年(7)

2008年(3)

分类: C/C++

2010-06-18 14:05:18

终端接口(Terminal interface)

通过调用一组称之为“通用终端接口”(General terminal interface,or GTI)的函数可以很好的对终端进行操作。这些函数的调用和对数据进行读写的函数调用是有区别的。这使得数据的(读/写)接口很干净,同时仍然允许对终端的行为进行更细微的调控。这话不是说终端的 I/O 接口上空无一物,相反的,它可以和各种千变万化的硬件设备打交道。

用 UNIX 的语言来说,控制接口设置了一个“行业帮规”(line discipline),这使得程序在定义终端驱动的操作行为方面给予了极大的灵活性(considerable flexibility)。 

可以控制的主要功能

行编辑(line editing):选择是否允许用 backspace 键进行编辑

缓冲(buffering): 选择是否立即读取字符还是在一个可配置的延迟后再读

回显(Echo):允许控制回显,如读取密码时

CR/LR:为输入/输出所做的映射:当打印一个换行符(\n, line feed character)时因该如何处理

线速度(line speed):在 PC 控制台上很少用到,这个线速度对于调制解调器和串行线上的终端就很重要了
termios 结构
termios 是由 POSIX 指定的标准接口并且它与 system V 的接口 termio 很象。终端接口由对结构体 termios 里的值设置以及一组函数进行控制。这些都定义在 termios.h 头文件中。

使用定义在 termios.h 中的函数需要链接一个合适的函数库。依据你的安装系统,这个可能是个标准的 C 库函数或者是 cruses 辅助性函数库。如果有必要,则在编译一些程序时,需要在编译命令行的末尾加上 -lcurses 。在一些较老的 linux 系统上,curses 库以一个名为 "new curses" 的版本提供出来。在这种情况下,库名和链接参数分别为 ncurses  -lncurses 

可以操控影响到终端的设置值划分为以下几组不同的工作模式:
输入模式(input)
输出模式(output)
控制模式(control)
本地模式(local)
特殊控制字符(special control characters)

一个最简单的 termios 结构的典型定义如下(X/Open 技术规范允许添加附加的数据域):
引用
#include
struct termios {
    tcflag_t c_iflag;
    tcflag_t c_oflag;
    tcflag_t c_cflag;
    tcflag_t c_lflag;
    cc_t     c_cc[NCCS];
};

结构体中的成员和上面列表中的五种工作模式对应(如 c_iflag 中的 i 就是 input 模式的头一个字母)。

可以通过调用 tcgetattr() 函数对 termios 结构体进行初始化,函数原型为
引用
#include
int tcgetattr(int fd, struct termios *termios_p);


这个调用把终端接口变量的当前值写到由 termios_p 指针所指向的 termios 结构体中。如果这些值稍后被改变了,你可以用 tcsetattr() 函数对终端接口进行重新配置,tcsetattr() 函数原型如下
引用
#include
int tcsetattr(int fd, int actions, const struct termios *termios_p);


上面的 actions 参数控制着“做怎样的改变”。三种方式是
TCSANOW: 立即对值进行修改
TCSADRAIN: 在当前输出完成后再对值进行修改
TCSAFLUSH: 在当前输出完成后再对值进行修改,但还要丢弃当前的可用输入数据和尚未从 read 调用的返回值

注意:程序需在运行完毕后要把终端设置恢复到其启动前的值,这点相当重要。先保存这些值然后在程序结束后恢复这些值永远是程序的责任。
输入模式(input modes)
输入模式控制输入(通过在串行口或键盘上的终端驱动(terminal driver)接收字符)在传递给程序之前如何处理。你可以通过设置 termios 结构体里的成员 c_iflag 标志来控制输入。所有的标志被定义成宏的形式并且它们可以进行 OR 运算。对于所有的终端模式情况都是这样的(This is the case for all the terminal modes)。

 c_iflag 定义的宏

BRKINT : 当在输入行上检测到一个终止条件(如连接丢失)时产生一个中断

IGNBRK : 忽略输入行上的终止条件

ICRNL : 把接收到的回车符转换成一个换行符(newline)

IGNCR : 忽略接收到的回车符(carriage returns)

INLCR : 把接收到的换行符转换成回车符

IGNPAR : 忽略带奇偶校验错误的字符

INPCK : 在接收到字符时进行奇偶校验

PARMRK : 标记奇偶错误

ISTRIP : 对所有接收到的字符都设置为 7 个二进制位

IXOFF : 激活对输入数据的软件流(software flow)控制

IXON : 激活对输出数据的软件流控制

注意:如果 BRKINT 和 IGNBRK 都没设置,那么在读取输入行时就会读到一个 NULL(0x00) 字符

用户一般不需要经常改变输入模式,因为默认值通常在很多情况下都是合适的。
输出模式(output modes)
这些模式控制如何出离输出字符。也就是,由程序发出来的的字符在传送到串行口或屏幕之前都经过了怎样的处理。正如所预料的,这在许多方面和输入模式都是像对应的。它还有几个附加的标志,它们主要关心并考虑那些慢速终端,这些终端在处理如回车符时需要花费一些时间。所有这些看起来显得有些多余(因为现在的终端已经快了很多),或者为什么不干脆用 terminfo 终端性能数据库?

通过设置 termios 结构体中的 c_oflag 标志可以控制输出模式。可用于 c_oflag 的宏为:

OPOST: 打开输出处理

ONLCR: 把输出中的换行符转换为回车符和行进纸符两个字符

OCRNL: 把输出中的回车符转换为换行符

ONOCR: 在第0列不允许对输出进行回车

ONLRET: 换行符等同于回车符

OFILL: 发送填充字符(fill characters)以提供延迟

FEDEL: 使用 DEL 而不是 NULL 作为填充字符

NLDLY : 换行符延迟选择

CRDLY: 回车符延迟选择

TABDLY: Tab 延迟选择

BSDLY: Backspac 延迟选择

VTDLY: 垂直制表(vertical tab)符延迟选择

FFDLY: 换页符(form feed)延迟选择

注意:如果 OPOST 没有设置,则所有的其它标志都被忽略。

输出模式其实也没有经常使用。
控制模式(control mode)
控制模式控制着终端的硬件特性。通过设置 termios 结构体中的 c_cflag 成员来指明控制模式,相关宏意义如下:

CLOCAL: 忽略任何调制解调器状态行

CREAD: 激活字符接收回执(receipt)功能

CS5: 在发送和接收的字符中使用 5 个二进制位

CS6: 在发送和接收的字符中使用 6 个二进制位

CS7: 在发送和接收的字符中使用 7 个二进制位

CS8: 在发送和接收的字符中使用 8 个二进制位

CSTOPB : 每个字符使用两个而不是一个停止位

HUPCL: 关闭操作时挂断调制解调器

PARENB : 激活奇偶校验(校验码)的检测和生成

PARODD : 使用奇校验而不是偶校验

注意: 如果 HUPCL 设置,当终端驱动检测到与终端对影的最后一个文件描述符关闭,它会设置调制解调器控制线挂断电话线路。

虽然控制模式可能用来与终端会话,但是其主要还是用在串行线和调制解调器连接的这种情况里。一般说来(normally),改变终端的配置比通过使用 termios 控制模式改变默认的线路连接行为会相对容易一些.
本地模式(local modes)
这些模式控制终端的不同特性.通过设置 termios 结构体中的 c_lfalg 成员来指定本地模式.相应的宏意义如下:

ECHO : 使能输入字符本地回显

ECHOE : 在收到 ERASE 时执行后退(Backspace), 空格(Space), 以及后退空格的动作组合

ECHOK : 在接收到 KILL 字符时执行行删除动作

ECHONL : 回显换行符

ICANON : 使能标准输入处理

IEXTEN : 使能实现特定函数(implementation-specific)功能

ISIG : 使能信号

NOFLSH : 禁止清空队列动作

TOSTOP : 在试图进行写操作之前给后台进程发送一个信号

两个比较重要的标志是 ECHO 和 ICANON .

ECHO 允许强制回显用户敲入的字符; 

ECANON 在两个明显不同的处理接收字符模式间对终端进行切换. 如果置位了 ICANON 标志, 就表示该输入行处于授权处理模式(canonical mode),否则处于非授权处理模式(non-canonical mode).
特殊控制字符(special control characters)
特殊控制字符是一个字符集,如 Ctrl+C, 当用户输入时,终端会采取特殊的动作响应。
termios 结构体中的成员 c_cc 数组包含着的字符都会映射到每一个被支持的函数。每一个字符的位置(在数组中的索引)由一个宏来定义,但这里并没有限制它们一定就是控制字符。

依据终端是否设置为授权处理或标准模式(canonical mode),也就是说,是否设置了 c_clfag 中的ICANON 标志,c_cc 数组的两个差别很大的用法。

对于标准模式,数组下标(indeces)有
VEOF : EOF 字符
VEOL : EOL 字符
VERASE : ERASE 字符
VINTR : INTR 字符
VKILL : KILL 字符
VQUIT : QUIT 字符
QSUSP : SUSP 字符
VSTART : START 字符
VSTOP : STOP 字符

对于非标准模式(non-canonical),数组下标为
VINTR : INTR 字符
VMIN : MIN 值
VQUIT : QUIT 字符
VSUSP : SUSP 字符
VTIME : TIME 值
VSTART : START 字符
VSTOP : STOP 字符

因为特殊字符和非标准值对于更高级的输入字符处理很重要,所以分述如下:

INTR              这个字符将引起终端驱动程序向连接到终端的进程发送一个 SIGINT 的信号

QUIT              这个字符将引起终端驱动程序向连接到终端的进程发送一个 SIGQUIT 的信号

ERASE             这个字符将引起终端驱动程序删除掉输入行中的最后一个字符

KILL               这个字符将引起终端驱动车呢根须删除掉整个输入行

EOF               引起终端驱动程序将输入行上的所有字符传送到读取输入程序中。如果输入行为空,
                  read 调用将返回 0 个字符,就好像 read 试图读取一个文件尾一样

SUSP              引起终端驱动程序给连接到终端的进程发送一个 SIGSUSP 的信号。如果 UNIX 支持作业控制功能,
                  则当前的应用程序将被挂起

STOP             这个字符的作用是“截流”(flow off);阻止继续向终端输出。它用来支持 XON/XOFF 控制流,
                  通常被设置为XOFF 字符,Ctrl+S.

START            在 STOP 字符后重新启动输出,常常是 ASCII 的 XON 字符
TIME 和 MIN 值
TIME 和 MIN 的值仅用在非标准模式下并且两者结合起来控制输入的读取。此外,它们还控制当一个程序试图去读一个与一终端相关联的文件描述符时将会发生什么样的事情。

这里有四种情况

1、MIN = 0 和 TIME = 0

在这种情况下, read 将总是立即返回。如果有等待处理的字符,它们就会被返回。如果没有可供处理的字符,read 将什么也读不到并返回 0 .

2、MIN = 0 和 TIME > 0

在这种情况下,只要有效字符一出现或经过 TIME 个十分之一秒,read 调用就会立刻返回。如果因为超时而没读到字符,read 调用就返回 0。否则,它就返回读到的字符个数。

3、MIN > 0 和 TIME = 0

在这种情况下,read 调用将等到读取到 MIN 个字符时才返回,并且返回的是读到的字符个数。到达文件末尾时返回 0。

4、MIN > 0 和 TIME > 0

这种情况比较复杂。当调用 read 时,它等待接收到一个字符。当接收到第一个字符以及后续各个字符时,会启动一个字符时间间隔计时器(如果计时器已经启动就会重启它)。read 调用在 MIN 个字符准备好或在超出 TIME 个十分之一秒的字符间隔时间时就会返回。这对于区分“单独按下 Escape 键”和“一个功能键转义序列(scape sequence)的开始”是很有用的。需要注意的是,网络通讯和高处理器负荷会把这种精细安排的时间信息清除掉。

通过设置非标准模式和使用 MIN 与 TIME 值,程序可以对输入的字符进行逐个的处理。
从 shell 里访问终端模式
如果你正在使用 shell 而你又希望看到 termios 的设置,可以使用如下命令:
引用
[root@localhost ~]# stty -a
speed 38400 baud; rows 24; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?;
swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W;
lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc ixany imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke


在上面,可以看到 EOF 字符对应着 Ctrl+D(eof = ^D) 并且使能了回显功能(echoe). 在做终端控制实验时,很容易把终端弄成一个非标准状态, 这就会造成使用起来很困难. 但是有几个办法可以使你摆脱(way out)这个困境: 

第一个方法,如果你的 stty 版本支持的话,使用以下命令:
引用
stty sane


如果你把回车键和换行符(用来结束命令行)之间的映射关系搞乱了, 你可以先键入 stty sane, 然后按入 Ctrl+J(这个组合键对应着换行符) , 而不是按下回车.

第二个方法是使用 stty -g 命令把当前的 stty 设置保存起来,在必要时再把原始值再读出来进行恢复.可以如下这么做:
引用
stty -g > save_stty
..
<修改终端设置>
..
stty $(cat save_stty)

上面,最后一条 stty 命令还是需要用 Ctrl+J 而不是回车键.在 shell 脚本程序里可以使用同样的技巧:
引用
save_stty = "$(stty -g)"

stty $save_stty


如果以上的这些招数都不管用了(if you're really stuck),第三个办法是打开一个不同的终端,使用 ps 命令找到你那个你弄得无法使用的 shell, 然后使用 kill HUP 强制把这个 shell 终止掉.因为 stty 参数总是在系统给出登录提示符前被重置,所以你能够在 stty 为初始值的情况下再一次正常的登录上机.
阅读(2029) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~